00001 <?php
00011 function wfSpecialUpload() {
00012 global $wgRequest;
00013 $form = new UploadForm( $wgRequest );
00014 $form->execute();
00015 }
00016
00021 class UploadForm {
00022 const SUCCESS = 0;
00023 const BEFORE_PROCESSING = 1;
00024 const LARGE_FILE_SERVER = 2;
00025 const EMPTY_FILE = 3;
00026 const MIN_LENGTH_PARTNAME = 4;
00027 const ILLEGAL_FILENAME = 5;
00028 const PROTECTED_PAGE = 6;
00029 const OVERWRITE_EXISTING_FILE = 7;
00030 const FILETYPE_MISSING = 8;
00031 const FILETYPE_BADTYPE = 9;
00032 const VERIFICATION_ERROR = 10;
00033 const UPLOAD_VERIFICATION_ERROR = 11;
00034 const UPLOAD_WARNING = 12;
00035 const INTERNAL_ERROR = 13;
00036
00040 var $mComment, $mLicense, $mIgnoreWarning, $mCurlError;
00041 var $mDestName, $mTempPath, $mFileSize, $mFileProps;
00042 var $mCopyrightStatus, $mCopyrightSource, $mReUpload, $mAction, $mUploadClicked;
00043 var $mSrcName, $mSessionKey, $mStashed, $mDesiredDestName, $mRemoveTempFile, $mSourceType;
00044 var $mDestWarningAck, $mCurlDestHandle;
00045 var $mLocalFile;
00046
00047 # Placeholders for text injection by hooks (must be HTML)
00048 # extensions should take care to _append_ to the present value
00049 var $uploadFormTextTop;
00050 var $uploadFormTextAfterSummary;
00051
00052 const SESSION_VERSION = 1;
00060 function UploadForm( &$request ) {
00061 global $wgAllowCopyUploads;
00062 $this->mDesiredDestName = $request->getText( 'wpDestFile' );
00063 $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' );
00064 $this->mComment = $request->getText( 'wpUploadDescription' );
00065 $this->mForReUpload = $request->getBool( 'wpForReUpload' );
00066 $this->mReUpload = $request->getCheck( 'wpReUpload' );
00067
00068 if( !$request->wasPosted() ) {
00069 # GET requests just give the main form; no data except destination
00070 # filename and description
00071 return;
00072 }
00073
00074 # Placeholders for text injection by hooks (empty per default)
00075 $this->uploadFormTextTop = "";
00076 $this->uploadFormTextAfterSummary = "";
00077 $this->mUploadClicked = $request->getCheck( 'wpUpload' );
00078
00079 $this->mLicense = $request->getText( 'wpLicense' );
00080 $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
00081 $this->mCopyrightSource = $request->getText( 'wpUploadSource' );
00082 $this->mWatchthis = $request->getBool( 'wpWatchthis' );
00083 $this->mSourceType = $request->getText( 'wpSourceType' );
00084 $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
00085
00086 $this->mAction = $request->getVal( 'action' );
00087
00088 $this->mSessionKey = $request->getInt( 'wpSessionKey' );
00089 if( !empty( $this->mSessionKey ) &&
00090 isset( $_SESSION['wsUploadData'][$this->mSessionKey]['version'] ) &&
00091 $_SESSION['wsUploadData'][$this->mSessionKey]['version'] == self::SESSION_VERSION ) {
00098 $data = $_SESSION['wsUploadData'][$this->mSessionKey];
00099 $this->mTempPath = $data['mTempPath'];
00100 $this->mFileSize = $data['mFileSize'];
00101 $this->mSrcName = $data['mSrcName'];
00102 $this->mFileProps = $data['mFileProps'];
00103 $this->mCurlError = 0;
00104 $this->mStashed = true;
00105 $this->mRemoveTempFile = false;
00106 } else {
00110 if( $wgAllowCopyUploads && $this->mSourceType == 'web' ) {
00111 $this->initializeFromUrl( $request );
00112 } else {
00113 $this->initializeFromUpload( $request );
00114 }
00115 }
00116 }
00117
00122 function initializeFromUpload( $request ) {
00123 $this->mTempPath = $request->getFileTempName( 'wpUploadFile' );
00124 $this->mFileSize = $request->getFileSize( 'wpUploadFile' );
00125 $this->mSrcName = $request->getFileName( 'wpUploadFile' );
00126 $this->mCurlError = $request->getUploadError( 'wpUploadFile' );
00127 $this->mSessionKey = false;
00128 $this->mStashed = false;
00129 $this->mRemoveTempFile = false;
00130 }
00131
00136 function initializeFromUrl( $request ) {
00137 global $wgTmpDirectory;
00138 $url = $request->getText( 'wpUploadFileURL' );
00139 $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' );
00140
00141 $this->mTempPath = $local_file;
00142 $this->mFileSize = 0; # Will be set by curlCopy
00143 $this->mCurlError = $this->curlCopy( $url, $local_file );
00144 $pathParts = explode( '/', $url );
00145 $this->mSrcName = array_pop( $pathParts );
00146 $this->mSessionKey = false;
00147 $this->mStashed = false;
00148
00149
00150 $this->mRemoveTempFile = file_exists( $local_file );
00151 }
00152
00157 private function curlCopy( $url, $dest ) {
00158 global $wgUser, $wgOut, $wgHTTPProxy;
00159
00160 if( !$wgUser->isAllowed( 'upload_by_url' ) ) {
00161 $wgOut->permissionRequired( 'upload_by_url' );
00162 return true;
00163 }
00164
00165 # Maybe remove some pasting blanks :-)
00166 $url = trim( $url );
00167 if( stripos($url, 'http://') !== 0 && stripos($url, 'ftp://') !== 0 ) {
00168 # Only HTTP or FTP URLs
00169 $wgOut->showErrorPage( 'upload-proto-error', 'upload-proto-error-text' );
00170 return true;
00171 }
00172
00173 # Open temporary file
00174 $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" );
00175 if( $this->mCurlDestHandle === false ) {
00176 # Could not open temporary file to write in
00177 $wgOut->showErrorPage( 'upload-file-error', 'upload-file-error-text');
00178 return true;
00179 }
00180
00181 $ch = curl_init();
00182 curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug
00183 curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout
00184 curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed
00185 curl_setopt( $ch, CURLOPT_URL, $url);
00186 if( $wgHTTPProxy ) {
00187 curl_setopt( $ch, CURLOPT_PROXY, $wgHTTPProxy );
00188 }
00189 curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) );
00190 curl_exec( $ch );
00191 $error = curl_errno( $ch ) ? true : false;
00192 $errornum = curl_errno( $ch );
00193
00194 curl_close( $ch );
00195
00196 fclose( $this->mCurlDestHandle );
00197 unset( $this->mCurlDestHandle );
00198 if( $error ) {
00199 unlink( $dest );
00200 if( wfEmptyMsg( "upload-curl-error$errornum", wfMsg("upload-curl-error$errornum") ) )
00201 $wgOut->showErrorPage( 'upload-misc-error', 'upload-misc-error-text' );
00202 else
00203 $wgOut->showErrorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" );
00204 }
00205
00206 return $error;
00207 }
00208
00215 function uploadCurlCallback( $ch, $data ) {
00216 global $wgMaxUploadSize;
00217 $length = strlen( $data );
00218 $this->mFileSize += $length;
00219 if( $this->mFileSize > $wgMaxUploadSize ) {
00220 return 0;
00221 }
00222 fwrite( $this->mCurlDestHandle, $data );
00223 return $length;
00224 }
00225
00230 function execute() {
00231 global $wgUser, $wgOut;
00232 global $wgEnableUploads;
00233
00234 # Check php's file_uploads setting
00235 if( !wfIniGetBool( 'file_uploads' ) ) {
00236 $wgOut->showErrorPage( 'uploaddisabled', 'php-uploaddisabledtext', array( $this->mDesiredDestName ) );
00237 return;
00238 }
00239
00240 # Check uploading enabled
00241 if( !$wgEnableUploads ) {
00242 $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext', array( $this->mDesiredDestName ) );
00243 return;
00244 }
00245
00246 # Check permissions
00247 if( !$wgUser->isAllowed( 'upload' ) ) {
00248 if( !$wgUser->isLoggedIn() ) {
00249 $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
00250 } else {
00251 $wgOut->permissionRequired( 'upload' );
00252 }
00253 return;
00254 }
00255
00256 # Check blocks
00257 if( $wgUser->isBlocked() ) {
00258 $wgOut->blockedPage();
00259 return;
00260 }
00261
00262 if( wfReadOnly() ) {
00263 $wgOut->readOnlyPage();
00264 return;
00265 }
00266
00267 if( $this->mReUpload ) {
00268 if( !$this->unsaveUploadedFile() ) {
00269 return;
00270 }
00271 # Because it is probably checked and shouldn't be
00272 $this->mIgnoreWarning = false;
00273
00274 $this->mainUploadForm();
00275 } else if( 'submit' == $this->mAction || $this->mUploadClicked ) {
00276 $this->processUpload();
00277 } else {
00278 $this->mainUploadForm();
00279 }
00280
00281 $this->cleanupTempFile();
00282 }
00283
00290 function processUpload(){
00291 global $wgUser, $wgOut, $wgFileExtensions, $wgLang;
00292 $details = null;
00293 $value = null;
00294 $value = $this->internalProcessUpload( $details );
00295
00296 switch($value) {
00297 case self::SUCCESS:
00298 $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
00299 break;
00300
00301 case self::BEFORE_PROCESSING:
00302 break;
00303
00304 case self::LARGE_FILE_SERVER:
00305 $this->mainUploadForm( wfMsgHtml( 'largefileserver' ) );
00306 break;
00307
00308 case self::EMPTY_FILE:
00309 $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) );
00310 break;
00311
00312 case self::MIN_LENGTH_PARTNAME:
00313 $this->mainUploadForm( wfMsgHtml( 'minlength1' ) );
00314 break;
00315
00316 case self::ILLEGAL_FILENAME:
00317 $filtered = $details['filtered'];
00318 $this->uploadError( wfMsgWikiHtml( 'illegalfilename', htmlspecialchars( $filtered ) ) );
00319 break;
00320
00321 case self::PROTECTED_PAGE:
00322 $wgOut->showPermissionsErrorPage( $details['permissionserrors'] );
00323 break;
00324
00325 case self::OVERWRITE_EXISTING_FILE:
00326 $errorText = $details['overwrite'];
00327 $this->uploadError( $wgOut->parse( $errorText ) );
00328 break;
00329
00330 case self::FILETYPE_MISSING:
00331 $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) );
00332 break;
00333
00334 case self::FILETYPE_BADTYPE:
00335 $finalExt = $details['finalExt'];
00336 $this->uploadError(
00337 wfMsgExt( 'filetype-banned-type',
00338 array( 'parseinline' ),
00339 htmlspecialchars( $finalExt ),
00340 $wgLang->commaList( $wgFileExtensions ),
00341 $wgLang->formatNum( count($wgFileExtensions) )
00342 )
00343 );
00344 break;
00345
00346 case self::VERIFICATION_ERROR:
00347 $veri = $details['veri'];
00348 $this->uploadError( $veri->toString() );
00349 break;
00350
00351 case self::UPLOAD_VERIFICATION_ERROR:
00352 $error = $details['error'];
00353 $this->uploadError( $error );
00354 break;
00355
00356 case self::UPLOAD_WARNING:
00357 $warning = $details['warning'];
00358 $this->uploadWarning( $warning );
00359 break;
00360
00361 case self::INTERNAL_ERROR:
00362 $internal = $details['internal'];
00363 $this->showError( $internal );
00364 break;
00365
00366 default:
00367 throw new MWException( __METHOD__ . ": Unknown value `{$value}`" );
00368 }
00369 }
00370
00379 function internalProcessUpload( &$resultDetails ) {
00380 global $wgUser;
00381
00382 if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) )
00383 {
00384 wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
00385 return self::BEFORE_PROCESSING;
00386 }
00387
00391 if( trim( $this->mSrcName ) == '' || empty( $this->mFileSize ) ) {
00392 return self::EMPTY_FILE;
00393 }
00394
00395
00396 if( $this->mCurlError ) {
00397 return self::BEFORE_PROCESSING;
00398 }
00399
00405 if( $this->mDesiredDestName ) {
00406 $basename = $this->mDesiredDestName;
00407 } else {
00408 $basename = $this->mSrcName;
00409 }
00410 $filtered = wfStripIllegalFilenameChars( $basename );
00411
00412
00413 $nt = Title::makeTitleSafe( NS_FILE, $filtered );
00414 if( is_null( $nt ) ) {
00415 $resultDetails = array( 'filtered' => $filtered );
00416 return self::ILLEGAL_FILENAME;
00417 }
00418 $filtered = $nt->getDBkey();
00419
00424 list( $partname, $ext ) = $this->splitExtensions( $filtered );
00425
00426 if( count( $ext ) ) {
00427 $finalExt = $ext[count( $ext ) - 1];
00428 } else {
00429 $finalExt = '';
00430 }
00431
00432 # If there was more than one "extension", reassemble the base
00433 # filename to prevent bogus complaints about length
00434 if( count( $ext ) > 1 ) {
00435 for( $i = 0; $i < count( $ext ) - 1; $i++ )
00436 $partname .= '.' . $ext[$i];
00437 }
00438
00439 if( strlen( $partname ) < 1 ) {
00440 return self::MIN_LENGTH_PARTNAME;
00441 }
00442
00443 $this->mLocalFile = wfLocalFile( $nt );
00444 $this->mDestName = $this->mLocalFile->getName();
00445
00450 $permErrors = $nt->getUserPermissionsErrors( 'edit', $wgUser );
00451 $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $wgUser );
00452 $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $wgUser ) );
00453
00454 if( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
00455
00456 $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
00457 $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
00458 $resultDetails = array( 'permissionserrors' => $permErrors );
00459 return self::PROTECTED_PAGE;
00460 }
00461
00465 $overwrite = $this->checkOverwrite( $this->mDestName );
00466 if( $overwrite !== true ) {
00467 $resultDetails = array( 'overwrite' => $overwrite );
00468 return self::OVERWRITE_EXISTING_FILE;
00469 }
00470
00471
00472 global $wgCheckFileExtensions, $wgStrictFileExtensions;
00473 global $wgFileExtensions, $wgFileBlacklist;
00474 if ($finalExt == '') {
00475 return self::FILETYPE_MISSING;
00476 } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) ||
00477 ($wgCheckFileExtensions && $wgStrictFileExtensions &&
00478 !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) {
00479 $resultDetails = array( 'finalExt' => $finalExt );
00480 return self::FILETYPE_BADTYPE;
00481 }
00482
00488 if( !$this->mStashed ) {
00489 $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $finalExt );
00490 $this->checkMacBinary();
00491 $veri = $this->verify( $this->mTempPath, $finalExt );
00492
00493 if( $veri !== true ) {
00494 $resultDetails = array( 'veri' => $veri );
00495 return self::VERIFICATION_ERROR;
00496 }
00497
00501 $error = '';
00502 if( !wfRunHooks( 'UploadVerification',
00503 array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
00504 $resultDetails = array( 'error' => $error );
00505 return self::UPLOAD_VERIFICATION_ERROR;
00506 }
00507 }
00508
00509
00513 if ( ! $this->mIgnoreWarning ) {
00514 $warning = '';
00515
00516 $comparableName = str_replace( ' ', '_', $basename );
00517 global $wgCapitalLinks, $wgContLang;
00518 if ( $wgCapitalLinks ) {
00519 $comparableName = $wgContLang->ucfirst( $comparableName );
00520 }
00521
00522 if( $comparableName !== $filtered ) {
00523 $warning .= '<li>'.wfMsgHtml( 'badfilename', htmlspecialchars( $this->mDestName ) ).'</li>';
00524 }
00525
00526 global $wgCheckFileExtensions;
00527 if ( $wgCheckFileExtensions ) {
00528 if ( !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) {
00529 global $wgLang;
00530 $warning .= '<li>' .
00531 wfMsgExt( 'filetype-unwanted-type',
00532 array( 'parseinline' ),
00533 htmlspecialchars( $finalExt ),
00534 $wgLang->commaList( $wgFileExtensions ),
00535 $wgLang->formatNum( count($wgFileExtensions) )
00536 ) . '</li>';
00537 }
00538 }
00539
00540 global $wgUploadSizeWarning;
00541 if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) {
00542 $skin = $wgUser->getSkin();
00543 $wsize = $skin->formatSize( $wgUploadSizeWarning );
00544 $asize = $skin->formatSize( $this->mFileSize );
00545 $warning .= '<li>' . wfMsgHtml( 'large-file', $wsize, $asize ) . '</li>';
00546 }
00547 if ( $this->mFileSize == 0 ) {
00548 $warning .= '<li>'.wfMsgHtml( 'emptyfile' ).'</li>';
00549 }
00550
00551 if ( !$this->mDestWarningAck ) {
00552 $warning .= self::getExistsWarning( $this->mLocalFile );
00553 }
00554
00555 $warning .= $this->getDupeWarning( $this->mTempPath, $finalExt, $nt );
00556
00557 if( $warning != '' ) {
00562 $resultDetails = array( 'warning' => $warning );
00563 return self::UPLOAD_WARNING;
00564 }
00565 }
00566
00571 if( !$this->mForReUpload ) {
00572 $pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
00573 $this->mCopyrightStatus, $this->mCopyrightSource );
00574 }
00575
00576 $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText,
00577 File::DELETE_SOURCE, $this->mFileProps );
00578 if ( !$status->isGood() ) {
00579 $resultDetails = array( 'internal' => $status->getWikiText() );
00580 return self::INTERNAL_ERROR;
00581 } else {
00582 if ( $this->mWatchthis ) {
00583 global $wgUser;
00584 $wgUser->addWatch( $this->mLocalFile->getTitle() );
00585 }
00586
00587 $img = null;
00588 wfRunHooks( 'UploadComplete', array( &$this ) );
00589 return self::SUCCESS;
00590 }
00591 }
00592
00599 static function getExistsWarning( $file ) {
00600 global $wgUser, $wgContLang;
00601
00602
00603 $warning = '';
00604 $align = $wgContLang->isRtl() ? 'left' : 'right';
00605
00606 if( strpos( $file->getName(), '.' ) == false ) {
00607 $partname = $file->getName();
00608 $rawExtension = '';
00609 } else {
00610 $n = strrpos( $file->getName(), '.' );
00611 $rawExtension = substr( $file->getName(), $n + 1 );
00612 $partname = substr( $file->getName(), 0, $n );
00613 }
00614
00615 $sk = $wgUser->getSkin();
00616
00617 if ( $rawExtension != $file->getExtension() ) {
00618
00619
00620
00621
00622
00623 $nt_lc = Title::makeTitle( NS_FILE, $partname . '.' . $file->getExtension() );
00624 $file_lc = wfLocalFile( $nt_lc );
00625 } else {
00626 $file_lc = false;
00627 }
00628
00629 if( $file->exists() ) {
00630 $dlink = $sk->makeKnownLinkObj( $file->getTitle() );
00631 if ( $file->allowInlineDisplay() ) {
00632 $dlink2 = $sk->makeImageLinkObj( $file->getTitle(), wfMsgExt( 'fileexists-thumb', 'parseinline' ),
00633 $file->getName(), $align, array(), false, true );
00634 } elseif ( !$file->allowInlineDisplay() && $file->isSafeFile() ) {
00635 $icon = $file->iconThumb();
00636 $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
00637 $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . $dlink . '</div>';
00638 } else {
00639 $dlink2 = '';
00640 }
00641
00642 $warning .= '<li>' . wfMsgExt( 'fileexists', array('parseinline','replaceafter'), $dlink ) . '</li>' . $dlink2;
00643
00644 } elseif( $file->getTitle()->getArticleID() ) {
00645 $lnk = $sk->makeKnownLinkObj( $file->getTitle(), '', 'redirect=no' );
00646 $warning .= '<li>' . wfMsgExt( 'filepageexists', array( 'parseinline', 'replaceafter' ), $lnk ) . '</li>';
00647 } elseif ( $file_lc && $file_lc->exists() ) {
00648 # Check if image with lowercase extension exists.
00649 # It's not forbidden but in 99% it makes no sense to upload the same filename with uppercase extension
00650 $dlink = $sk->makeKnownLinkObj( $nt_lc );
00651 if ( $file_lc->allowInlineDisplay() ) {
00652 $dlink2 = $sk->makeImageLinkObj( $nt_lc, wfMsgExt( 'fileexists-thumb', 'parseinline' ),
00653 $nt_lc->getText(), $align, array(), false, true );
00654 } elseif ( !$file_lc->allowInlineDisplay() && $file_lc->isSafeFile() ) {
00655 $icon = $file_lc->iconThumb();
00656 $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
00657 $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . $dlink . '</div>';
00658 } else {
00659 $dlink2 = '';
00660 }
00661
00662 $warning .= '<li>' .
00663 wfMsgExt( 'fileexists-extension', 'parsemag',
00664 $file->getTitle()->getPrefixedText(), $dlink ) .
00665 '</li>' . $dlink2;
00666
00667 } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' )
00668 && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) )
00669 {
00670 # Check for filenames like 50px- or 180px-, these are mostly thumbnails
00671 $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension );
00672 $file_thb = wfLocalFile( $nt_thb );
00673 if ($file_thb->exists() ) {
00674 # Check if an image without leading '180px-' (or similiar) exists
00675 $dlink = $sk->makeKnownLinkObj( $nt_thb);
00676 if ( $file_thb->allowInlineDisplay() ) {
00677 $dlink2 = $sk->makeImageLinkObj( $nt_thb,
00678 wfMsgExt( 'fileexists-thumb', 'parseinline' ),
00679 $nt_thb->getText(), $align, array(), false, true );
00680 } elseif ( !$file_thb->allowInlineDisplay() && $file_thb->isSafeFile() ) {
00681 $icon = $file_thb->iconThumb();
00682 $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
00683 $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' .
00684 $dlink . '</div>';
00685 } else {
00686 $dlink2 = '';
00687 }
00688
00689 $warning .= '<li>' . wfMsgExt( 'fileexists-thumbnail-yes', 'parsemag', $dlink ) .
00690 '</li>' . $dlink2;
00691 } else {
00692 # Image w/o '180px-' does not exists, but we do not like these filenames
00693 $warning .= '<li>' . wfMsgExt( 'file-thumbnail-no', 'parseinline' ,
00694 substr( $partname , 0, strpos( $partname , '-' ) +1 ) ) . '</li>';
00695 }
00696 }
00697
00698 $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist();
00699 # Do the match
00700 foreach( $filenamePrefixBlacklist as $prefix ) {
00701 if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
00702 $warning .= '<li>' . wfMsgExt( 'filename-bad-prefix', 'parseinline', $prefix ) . '</li>';
00703 break;
00704 }
00705 }
00706
00707 if ( $file->wasDeleted() && !$file->exists() ) {
00708 # If the file existed before and was deleted, warn the user of this
00709 # Don't bother doing so if the file exists now, however
00710 $ltitle = SpecialPage::getTitleFor( 'Log' );
00711 $llink = $sk->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ),
00712 'type=delete&page=' . $file->getTitle()->getPrefixedUrl() );
00713 $warning .= '<li>' . wfMsgWikiHtml( 'filewasdeleted', $llink ) . '</li>';
00714 }
00715 return $warning;
00716 }
00717
00724 static function ajaxGetExistsWarning( $filename ) {
00725 $file = wfFindFile( $filename );
00726 if( !$file ) {
00727
00728
00729 $file = wfLocalFile( $filename );
00730 }
00731 $s = ' ';
00732 if ( $file ) {
00733 $warning = self::getExistsWarning( $file );
00734 if ( $warning !== '' ) {
00735 $s = "<ul>$warning</ul>";
00736 }
00737 }
00738 return $s;
00739 }
00740
00747 public static function ajaxGetLicensePreview( $license ) {
00748 global $wgParser, $wgUser;
00749 $text = '{{' . $license . '}}';
00750 $title = Title::makeTitle( NS_FILE, 'Sample.jpg' );
00751 $options = ParserOptions::newFromUser( $wgUser );
00752
00753
00754 $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options );
00755 $output = $wgParser->parse( $text, $title, $options );
00756
00757 return $output->getText();
00758 }
00759
00764 function getDupeWarning( $tempfile, $extension, $destinationTitle ) {
00765 $hash = File::sha1Base36( $tempfile );
00766 $dupes = RepoGroup::singleton()->findBySha1( $hash );
00767 $archivedImage = new ArchivedFile( null, 0, $hash.".$extension" );
00768 if( $dupes ) {
00769 global $wgOut;
00770 $msg = "<gallery>";
00771 foreach( $dupes as $file ) {
00772 $title = $file->getTitle();
00773 # Don't throw the warning when the titles are the same, it's a reupload
00774 # and highly redundant.
00775 if ( !$title->equals( $destinationTitle ) || !$this->mForReUpload ) {
00776 $msg .= $title->getPrefixedText() .
00777 "|" . $title->getText() . "\n";
00778 }
00779 }
00780 $msg .= "</gallery>";
00781 return "<li>" .
00782 wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) .
00783 $wgOut->parse( $msg ) .
00784 "</li>\n";
00785 } elseif ( $archivedImage->getID() > 0 ) {
00786 global $wgOut;
00787 $name = Title::makeTitle( NS_FILE, $archivedImage->getName() )->getPrefixedText();
00788 return Xml::tags( 'li', null, wfMsgExt( 'file-deleted-duplicate', array( 'parseinline' ), array( $name ) ) );
00789 } else {
00790 return '';
00791 }
00792 }
00793
00799 public static function getFilenamePrefixBlacklist() {
00800 $blacklist = array();
00801 $message = wfMsgForContent( 'filename-prefix-blacklist' );
00802 if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) {
00803 $lines = explode( "\n", $message );
00804 foreach( $lines as $line ) {
00805
00806 $comment = substr( trim( $line ), 0, 1 );
00807 if ( $comment == '#' || $comment == '' ) {
00808 continue;
00809 }
00810
00811 $comment = strpos( $line, '#' );
00812 if ( $comment > 0 ) {
00813 $line = substr( $line, 0, $comment-1 );
00814 }
00815 $blacklist[] = trim( $line );
00816 }
00817 }
00818 return $blacklist;
00819 }
00820
00833 function saveTempUploadedFile( $saveName, $tempName ) {
00834 global $wgOut;
00835 $repo = RepoGroup::singleton()->getLocalRepo();
00836 $status = $repo->storeTemp( $saveName, $tempName );
00837 if ( !$status->isGood() ) {
00838 $this->showError( $status->getWikiText() );
00839 return false;
00840 } else {
00841 return $status->value;
00842 }
00843 }
00844
00854 function stashSession() {
00855 $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
00856
00857 if( !$stash ) {
00858 # Couldn't save the file.
00859 return false;
00860 }
00861
00862 $key = mt_rand( 0, 0x7fffffff );
00863 $_SESSION['wsUploadData'][$key] = array(
00864 'mTempPath' => $stash,
00865 'mFileSize' => $this->mFileSize,
00866 'mSrcName' => $this->mSrcName,
00867 'mFileProps' => $this->mFileProps,
00868 'version' => self::SESSION_VERSION,
00869 );
00870 return $key;
00871 }
00872
00878 function unsaveUploadedFile() {
00879 global $wgOut;
00880 if( !$this->mTempPath ) return true;
00881 $repo = RepoGroup::singleton()->getLocalRepo();
00882 $success = $repo->freeTemp( $this->mTempPath );
00883 if ( ! $success ) {
00884 $wgOut->showFileDeleteError( $this->mTempPath );
00885 return false;
00886 } else {
00887 return true;
00888 }
00889 }
00890
00891
00892
00897 function uploadError( $error ) {
00898 global $wgOut;
00899 $wgOut->addHTML( '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" );
00900 $wgOut->addHTML( '<span class="error">' . $error . '</span>' );
00901 }
00902
00911 function uploadWarning( $warning ) {
00912 global $wgOut;
00913 global $wgUseCopyrightUpload;
00914
00915 $this->mSessionKey = $this->stashSession();
00916 if( !$this->mSessionKey ) {
00917 # Couldn't save file; an error has been displayed so let's go.
00918 return;
00919 }
00920
00921 $wgOut->addHTML( '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" );
00922 $wgOut->addHTML( '<ul class="warning">' . $warning . "</ul>\n" );
00923
00924 $titleObj = SpecialPage::getTitleFor( 'Upload' );
00925
00926 if ( $wgUseCopyrightUpload ) {
00927 $copyright = Xml::hidden( 'wpUploadCopyStatus', $this->mCopyrightStatus ) . "\n" .
00928 Xml::hidden( 'wpUploadSource', $this->mCopyrightSource ) . "\n";
00929 } else {
00930 $copyright = '';
00931 }
00932
00933 $wgOut->addHTML(
00934 Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ),
00935 'enctype' => 'multipart/form-data', 'id' => 'uploadwarning' ) ) . "\n" .
00936 Xml::hidden( 'wpIgnoreWarning', '1' ) . "\n" .
00937 Xml::hidden( 'wpSessionKey', $this->mSessionKey ) . "\n" .
00938 Xml::hidden( 'wpUploadDescription', $this->mComment ) . "\n" .
00939 Xml::hidden( 'wpLicense', $this->mLicense ) . "\n" .
00940 Xml::hidden( 'wpDestFile', $this->mDesiredDestName ) . "\n" .
00941 Xml::hidden( 'wpWatchthis', $this->mWatchthis ) . "\n" .
00942 "{$copyright}<br />" .
00943 Xml::submitButton( wfMsg( 'ignorewarning' ), array ( 'name' => 'wpUpload', 'id' => 'wpUpload', 'checked' => 'checked' ) ) . ' ' .
00944 Xml::submitButton( wfMsg( 'reuploaddesc' ), array ( 'name' => 'wpReUpload', 'id' => 'wpReUpload' ) ) .
00945 Xml::closeElement( 'form' ) . "\n"
00946 );
00947 }
00948
00956 function mainUploadForm( $msg='' ) {
00957 global $wgOut, $wgUser, $wgLang, $wgMaxUploadSize;
00958 global $wgUseCopyrightUpload, $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview;
00959 global $wgRequest, $wgAllowCopyUploads;
00960 global $wgStylePath, $wgStyleVersion;
00961
00962 $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
00963 $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview;
00964
00965 $adc = wfBoolToStr( $useAjaxDestCheck );
00966 $alp = wfBoolToStr( $useAjaxLicensePreview );
00967 $autofill = wfBoolToStr( $this->mDesiredDestName == '' );
00968
00969 $wgOut->addScript( "<script type=\"text/javascript\">
00970 wgAjaxUploadDestCheck = {$adc};
00971 wgAjaxLicensePreview = {$alp};
00972 wgUploadAutoFill = {$autofill};
00973 </script>" );
00974 $wgOut->addScriptFile( 'upload.js' );
00975 $wgOut->addScriptFile( 'edit.js' );
00976
00977 if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) )
00978 {
00979 wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
00980 return false;
00981 }
00982
00983 if( $this->mDesiredDestName ) {
00984 $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
00985
00986 if( $title instanceof Title && ( $count = $title->isDeleted() ) > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) {
00987 $link = wfMsgExt(
00988 $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
00989 array( 'parse', 'replaceafter' ),
00990 $wgUser->getSkin()->makeKnownLinkObj(
00991 SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
00992 wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count )
00993 )
00994 );
00995 $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
00996 }
00997
00998
00999 if( $title instanceof Title && $title->isDeletedQuick() && !$title->exists() ) {
01000 $this->showDeletionLog( $wgOut, $title->getPrefixedText() );
01001 }
01002 }
01003
01004 $cols = intval($wgUser->getOption( 'cols' ));
01005
01006 if( $wgUser->getOption( 'editwidth' ) ) {
01007 $width = " style=\"width:100%\"";
01008 } else {
01009 $width = '';
01010 }
01011
01012 if ( '' != $msg ) {
01013 $sub = wfMsgHtml( 'uploaderror' );
01014 $wgOut->addHTML( "<h2>{$sub}</h2>\n" .
01015 "<span class='error'>{$msg}</span>\n" );
01016 }
01017 $wgOut->addHTML( '<div id="uploadtext">' );
01018 $wgOut->addWikiMsg( 'uploadtext', $this->mDesiredDestName );
01019 $wgOut->addHTML( "</div>\n" );
01020
01021 # Print a list of allowed file extensions, if so configured. We ignore
01022 # MIME type here, it's incomprehensible to most people and too long.
01023 global $wgCheckFileExtensions, $wgStrictFileExtensions,
01024 $wgFileExtensions, $wgFileBlacklist;
01025
01026 $allowedExtensions = '';
01027 if( $wgCheckFileExtensions ) {
01028 if( $wgStrictFileExtensions ) {
01029 # Everything not permitted is banned
01030 $extensionsList =
01031 '<div id="mw-upload-permitted">' .
01032 wfMsgWikiHtml( 'upload-permitted', $wgLang->commaList( $wgFileExtensions ) ) .
01033 "</div>\n";
01034 } else {
01035 # We have to list both preferred and prohibited
01036 $extensionsList =
01037 '<div id="mw-upload-preferred">' .
01038 wfMsgWikiHtml( 'upload-preferred', $wgLang->commaList( $wgFileExtensions ) ) .
01039 "</div>\n" .
01040 '<div id="mw-upload-prohibited">' .
01041 wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileBlacklist ) ) .
01042 "</div>\n";
01043 }
01044 } else {
01045 # Everything is permitted.
01046 $extensionsList = '';
01047 }
01048
01049 # Get the maximum file size from php.ini as $wgMaxUploadSize works for uploads from URL via CURL only
01050 # See http://www.php.net/manual/en/ini.core.php#ini.upload-max-filesize for possible values of upload_max_filesize
01051 $val = trim( ini_get( 'upload_max_filesize' ) );
01052 $last = strtoupper( ( substr( $val, -1 ) ) );
01053 switch( $last ) {
01054 case 'G':
01055 $val2 = substr( $val, 0, -1 ) * 1024 * 1024 * 1024;
01056 break;
01057 case 'M':
01058 $val2 = substr( $val, 0, -1 ) * 1024 * 1024;
01059 break;
01060 case 'K':
01061 $val2 = substr( $val, 0, -1 ) * 1024;
01062 break;
01063 default:
01064 $val2 = $val;
01065 }
01066 $val2 = $wgAllowCopyUploads ? min( $wgMaxUploadSize, $val2 ) : $val2;
01067 $maxUploadSize = '<div id="mw-upload-maxfilesize">' .
01068 wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ),
01069 $wgLang->formatSize( $val2 ) ) .
01070 "</div>\n";
01071
01072 $sourcefilename = wfMsgExt( 'sourcefilename', array( 'parseinline', 'escapenoentities' ) );
01073 $destfilename = wfMsgExt( 'destfilename', array( 'parseinline', 'escapenoentities' ) );
01074
01075 $msg = $this->mForReUpload ? 'filereuploadsummary' : 'fileuploadsummary';
01076 $summary = wfMsgExt( $msg, 'parseinline' );
01077
01078 $licenses = new Licenses();
01079 $license = wfMsgExt( 'license', array( 'parseinline' ) );
01080 $nolicense = wfMsgHtml( 'nolicense' );
01081 $licenseshtml = $licenses->getHtml();
01082
01083 $ulb = wfMsgHtml( 'uploadbtn' );
01084
01085
01086 $titleObj = SpecialPage::getTitleFor( 'Upload' );
01087
01088 $encDestName = htmlspecialchars( $this->mDesiredDestName );
01089
01090 $watchChecked = $this->watchCheck() ? 'checked="checked"' : '';
01091 # Re-uploads should not need "file exist already" warnings
01092 $warningChecked = ($this->mIgnoreWarning || $this->mForReUpload) ? 'checked="checked"' : '';
01093
01094
01095 if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) {
01096 $filename_form =
01097 "<input type='radio' id='wpSourceTypeFile' name='wpSourceType' value='file' " .
01098 "onchange='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\")' checked='checked' />" .
01099 "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " .
01100 "onfocus='" .
01101 "toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\");" .
01102 "toggle_element_check(\"wpSourceTypeFile\",\"wpSourceTypeURL\")' " .
01103 "onchange='fillDestFilename(\"wpUploadFile\")' size='60' />" .
01104 wfMsgHTML( 'upload_source_file' ) . "<br/>" .
01105 "<input type='radio' id='wpSourceTypeURL' name='wpSourceType' value='web' " .
01106 "onchange='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\")' />" .
01107 "<input tabindex='1' type='text' name='wpUploadFileURL' id='wpUploadFileURL' " .
01108 "onfocus='" .
01109 "toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\");" .
01110 "toggle_element_check(\"wpSourceTypeURL\",\"wpSourceTypeFile\")' " .
01111 "onchange='fillDestFilename(\"wpUploadFileURL\")' size='60' disabled='disabled' />" .
01112 wfMsgHtml( 'upload_source_url' ) ;
01113 } else {
01114 $filename_form =
01115 "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " .
01116 ($this->mDesiredDestName?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") .
01117 "size='60' />" .
01118 "<input type='hidden' name='wpSourceType' value='file' />" ;
01119 }
01120 if ( $useAjaxDestCheck ) {
01121 $warningRow = "<tr><td colspan='2' id='wpDestFile-warning'> </td></tr>";
01122 $destOnkeyup = 'onkeyup="wgUploadWarningObj.keypress();"';
01123 } else {
01124 $warningRow = '';
01125 $destOnkeyup = '';
01126 }
01127 $encComment = htmlspecialchars( $this->mComment );
01128
01129
01130 $wgOut->addHTML(
01131 Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL(),
01132 'enctype' => 'multipart/form-data', 'id' => 'mw-upload-form' ) ) .
01133 Xml::openElement( 'fieldset' ) .
01134 Xml::element( 'legend', null, wfMsg( 'upload' ) ) .
01135 Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-upload-table' ) ) .
01136 "<tr>
01137 {$this->uploadFormTextTop}
01138 <td class='mw-label'>
01139 <label for='wpUploadFile'>{$sourcefilename}</label>
01140 </td>
01141 <td class='mw-input'>
01142 {$filename_form}
01143 </td>
01144 </tr>
01145 <tr>
01146 <td></td>
01147 <td>
01148 {$maxUploadSize}
01149 {$extensionsList}
01150 </td>
01151 </tr>
01152 <tr>
01153 <td class='mw-label'>
01154 <label for='wpDestFile'>{$destfilename}</label>
01155 </td>
01156 <td class='mw-input'>"
01157 );
01158 if( $this->mForReUpload ) {
01159 $wgOut->addHTML(
01160 Xml::hidden( 'wpDestFile', $this->mDesiredDestName, array('id'=>'wpDestFile','tabindex'=>2) ) .
01161 "<tt>" .
01162 $encDestName .
01163 "</tt>"
01164 );
01165 }
01166 else {
01167 $wgOut->addHTML(
01168 "<input tabindex='2' type='text' name='wpDestFile' id='wpDestFile' size='60'
01169 value=\"{$encDestName}\" onchange='toggleFilenameFiller()' $destOnkeyup />"
01170 );
01171 }
01172
01173
01174 $wgOut->addHTML(
01175 "</td>
01176 </tr>
01177 <tr>
01178 <td class='mw-label'>
01179 <label for='wpUploadDescription'>{$summary}</label>
01180 </td>
01181 <td class='mw-input'>
01182 <textarea tabindex='3' name='wpUploadDescription' id='wpUploadDescription' rows='6'
01183 cols='{$cols}'{$width}>$encComment</textarea>
01184 {$this->uploadFormTextAfterSummary}
01185 </td>
01186 </tr>
01187 <tr>"
01188 );
01189 # Re-uploads should not need license info
01190 if ( !$this->mForReUpload && $licenseshtml != '' ) {
01191 global $wgStylePath;
01192 $wgOut->addHTML( "
01193 <td class='mw-label'>
01194 <label for='wpLicense'>$license</label>
01195 </td>
01196 <td class='mw-input'>
01197 <select name='wpLicense' id='wpLicense' tabindex='4'
01198 onchange='licenseSelectorCheck()'>
01199 <option value=''>$nolicense</option>
01200 $licenseshtml
01201 </select>
01202 </td>
01203 </tr>
01204 <tr>"
01205 );
01206 if( $useAjaxLicensePreview ) {
01207 $wgOut->addHTML( "
01208 <td></td>
01209 <td id=\"mw-license-preview\"></td>
01210 </tr>
01211 <tr>"
01212 );
01213 }
01214 }
01215
01216 if ( !$this->mForReUpload && $wgUseCopyrightUpload ) {
01217 $filestatus = wfMsgExt( 'filestatus', 'escapenoentities' );
01218 $copystatus = htmlspecialchars( $this->mCopyrightStatus );
01219 $filesource = wfMsgExt( 'filesource', 'escapenoentities' );
01220 $uploadsource = htmlspecialchars( $this->mCopyrightSource );
01221
01222 $wgOut->addHTML( "
01223 <td class='mw-label' style='white-space: nowrap;'>
01224 <label for='wpUploadCopyStatus'>$filestatus</label></td>
01225 <td class='mw-input'>
01226 <input tabindex='5' type='text' name='wpUploadCopyStatus' id='wpUploadCopyStatus'
01227 value=\"$copystatus\" size='60' />
01228 </td>
01229 </tr>
01230 <tr>
01231 <td class='mw-label'>
01232 <label for='wpUploadCopyStatus'>$filesource</label>
01233 </td>
01234 <td class='mw-input'>
01235 <input tabindex='6' type='text' name='wpUploadSource' id='wpUploadCopyStatus'
01236 value=\"$uploadsource\" size='60' />
01237 </td>
01238 </tr>
01239 <tr>"
01240 );
01241 }
01242
01243 $wgOut->addHTML( "
01244 <td></td>
01245 <td>
01246 <input tabindex='7' type='checkbox' name='wpWatchthis' id='wpWatchthis' $watchChecked value='true' />
01247 <label for='wpWatchthis'>" . wfMsgHtml( 'watchthisupload' ) . "</label>
01248 <input tabindex='8' type='checkbox' name='wpIgnoreWarning' id='wpIgnoreWarning' value='true' $warningChecked />
01249 <label for='wpIgnoreWarning'>" . wfMsgHtml( 'ignorewarnings' ) . "</label>
01250 </td>
01251 </tr>
01252 $warningRow
01253 <tr>
01254 <td></td>
01255 <td class='mw-input'>
01256 <input tabindex='9' type='submit' name='wpUpload' value=\"{$ulb}\"" .
01257 $wgUser->getSkin()->tooltipAndAccesskey( 'upload' ) . " />
01258 </td>
01259 </tr>
01260 <tr>
01261 <td></td>
01262 <td class='mw-input'>"
01263 );
01264 $wgOut->addHTML( '<div class="mw-editTools">' );
01265 $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) );
01266 $wgOut->addHTML( '</div>' );
01267 $wgOut->addHTML( "
01268 </td>
01269 </tr>" .
01270 Xml::closeElement( 'table' ) .
01271 Xml::hidden( 'wpDestFileWarningAck', '', array( 'id' => 'wpDestFileWarningAck' ) ) .
01272 Xml::hidden( 'wpForReUpload', $this->mForReUpload, array( 'id' => 'wpForReUpload' ) ) .
01273 Xml::closeElement( 'fieldset' ) .
01274 Xml::closeElement( 'form' )
01275 );
01276 $uploadfooter = wfMsgNoTrans( 'uploadfooter' );
01277 if( $uploadfooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadfooter ) ){
01278 $wgOut->addWikiText( '<div id="mw-upload-footer-message">' . $uploadfooter . '</div>' );
01279 }
01280 }
01281
01282
01283
01295 function watchCheck() {
01296 global $wgUser;
01297 if( $wgUser->getOption( 'watchdefault' ) ) {
01298
01299 return true;
01300 }
01301
01302 $local = wfLocalFile( $this->mDesiredDestName );
01303 if( $local && $local->exists() ) {
01304
01305
01306 return $local->getTitle()->userIsWatching();
01307 } else {
01308
01309 return $wgUser->getOption( 'watchcreations' );
01310 }
01311 }
01312
01321 public function splitExtensions( $filename ) {
01322 $bits = explode( '.', $filename );
01323 $basename = array_shift( $bits );
01324 return array( $basename, $bits );
01325 }
01326
01335 function checkFileExtension( $ext, $list ) {
01336 return in_array( strtolower( $ext ), $list );
01337 }
01338
01347 public function checkFileExtensionList( $ext, $list ) {
01348 foreach( $ext as $e ) {
01349 if( in_array( strtolower( $e ), $list ) ) {
01350 return true;
01351 }
01352 }
01353 return false;
01354 }
01355
01363 function verify( $tmpfile, $extension ) {
01364 #magically determine mime type
01365 $magic = MimeMagic::singleton();
01366 $mime = $magic->guessMimeType($tmpfile,false);
01367
01368
01369 #check mime type, if desired
01370 global $wgVerifyMimeType;
01371 if ($wgVerifyMimeType) {
01372 wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n");
01373 #check mime type against file extension
01374 if( !self::verifyExtension( $mime, $extension ) ) {
01375 return new WikiErrorMsg( 'uploadcorrupt' );
01376 }
01377
01378 #check mime type blacklist
01379 global $wgMimeTypeBlacklist;
01380 if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) ) {
01381 if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
01382 return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) );
01383 }
01384
01385 # Check IE type
01386 $fp = fopen( $tmpfile, 'rb' );
01387 $chunk = fread( $fp, 256 );
01388 fclose( $fp );
01389 $extMime = $magic->guessTypesForExtension( $extension );
01390 $ieTypes = $magic->getIEMimeTypes( $tmpfile, $chunk, $extMime );
01391 foreach ( $ieTypes as $ieType ) {
01392 if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
01393 return new WikiErrorMsg( 'filetype-bad-ie-mime', $ieType );
01394 }
01395 }
01396 }
01397 }
01398
01399 #check for htmlish code and javascript
01400 if( $this->detectScript ( $tmpfile, $mime, $extension ) ) {
01401 return new WikiErrorMsg( 'uploadscripted' );
01402 }
01403 if( $extension == 'svg' || $mime == 'image/svg+xml' ) {
01404 if( $this->detectScriptInSvg( $tmpfile ) ) {
01405 return new WikiErrorMsg( 'uploadscripted' );
01406 }
01407 }
01408
01412 $virus= $this->detectVirus($tmpfile);
01413 if ( $virus ) {
01414 return new WikiErrorMsg( 'uploadvirus', htmlspecialchars($virus) );
01415 }
01416
01417 wfDebug( __METHOD__.": all clear; passing.\n" );
01418 return true;
01419 }
01420
01428 static function verifyExtension( $mime, $extension ) {
01429 $magic = MimeMagic::singleton();
01430
01431 if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
01432 if ( ! $magic->isRecognizableExtension( $extension ) ) {
01433 wfDebug( __METHOD__.": passing file with unknown detected mime type; " .
01434 "unrecognized extension '$extension', can't verify\n" );
01435 return true;
01436 } else {
01437 wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ".
01438 "recognized extension '$extension', so probably invalid file\n" );
01439 return false;
01440 }
01441
01442 $match= $magic->isMatchingExtension($extension,$mime);
01443
01444 if ($match===NULL) {
01445 wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" );
01446 return true;
01447 } elseif ($match===true) {
01448 wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" );
01449
01450 #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
01451 return true;
01452
01453 } else {
01454 wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" );
01455 return false;
01456 }
01457 }
01458
01459
01470 function detectScript($file, $mime, $extension) {
01471 global $wgAllowTitlesInSVG;
01472
01473 #ugly hack: for text files, always look at the entire file.
01474 #For binarie field, just check the first K.
01475
01476 if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file );
01477 else {
01478 $fp = fopen( $file, 'rb' );
01479 $chunk = fread( $fp, 1024 );
01480 fclose( $fp );
01481 }
01482
01483 $chunk= strtolower( $chunk );
01484
01485 if (!$chunk) return false;
01486
01487 #decode from UTF-16 if needed (could be used for obfuscation).
01488 if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE";
01489 elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE";
01490 else $enc= NULL;
01491
01492 if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk);
01493
01494 $chunk= trim($chunk);
01495
01496 #FIXME: convert from UTF-16 if necessarry!
01497
01498 wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n");
01499
01500 #check for HTML doctype
01501 if (eregi("<!DOCTYPE *X?HTML",$chunk)) return true;
01502
01519 $tags = array(
01520 '<a href',
01521 '<body',
01522 '<head',
01523 '<html', #also in safari
01524 '<img',
01525 '<pre',
01526 '<script', #also in safari
01527 '<table'
01528 );
01529 if( ! $wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) {
01530 $tags[] = '<title';
01531 }
01532
01533 foreach( $tags as $tag ) {
01534 if( false !== strpos( $chunk, $tag ) ) {
01535 return true;
01536 }
01537 }
01538
01539
01540
01541
01542
01543 #resolve entity-refs to look at attributes. may be harsh on big files... cache result?
01544 $chunk = Sanitizer::decodeCharReferences( $chunk );
01545
01546 #look for script-types
01547 if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true;
01548
01549 #look for html-style script-urls
01550 if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true;
01551
01552 #look for css-style script-urls
01553 if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true;
01554
01555 wfDebug("SpecialUpload::detectScript: no scripts found\n");
01556 return false;
01557 }
01558
01559 function detectScriptInSvg( $filename ) {
01560 $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) );
01561 return $check->filterMatch;
01562 }
01563
01567 function checkSvgScriptCallback( $element, $attribs ) {
01568 $stripped = $this->stripXmlNamespace( $element );
01569
01570 if( $stripped == 'script' ) {
01571 wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
01572 return true;
01573 }
01574
01575 foreach( $attribs as $attrib => $value ) {
01576 $stripped = $this->stripXmlNamespace( $attrib );
01577 if( substr( $stripped, 0, 2 ) == 'on' ) {
01578 wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" );
01579 return true;
01580 }
01581 if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
01582 wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" );
01583 return true;
01584 }
01585 }
01586 }
01587
01588 private function stripXmlNamespace( $name ) {
01589
01590 $parts = explode( ':', strtolower( $name ) );
01591 return array_pop( $parts );
01592 }
01593
01604 function detectVirus($file) {
01605 global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut;
01606
01607 if ( !$wgAntivirus ) {
01608 wfDebug( __METHOD__.": virus scanner disabled\n");
01609 return NULL;
01610 }
01611
01612 if ( !$wgAntivirusSetup[$wgAntivirus] ) {
01613 wfDebug( __METHOD__.": unknown virus scanner: $wgAntivirus\n" );
01614 $wgOut->wrapWikiMsg( '<div class="error">$1</div>', array( 'virus-badscanner', $wgAntivirus ) );
01615 return wfMsg('virus-unknownscanner') . " $wgAntivirus";
01616 }
01617
01618 # look up scanner configuration
01619 $command = $wgAntivirusSetup[$wgAntivirus]["command"];
01620 $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"];
01621 $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ?
01622 $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null;
01623
01624 if ( strpos( $command,"%f" ) === false ) {
01625 # simple pattern: append file to scan
01626 $command .= " " . wfEscapeShellArg( $file );
01627 } else {
01628 # complex pattern: replace "%f" with file to scan
01629 $command = str_replace( "%f", wfEscapeShellArg( $file ), $command );
01630 }
01631
01632 wfDebug( __METHOD__.": running virus scan: $command \n" );
01633
01634 # execute virus scanner
01635 $exitCode = false;
01636
01637 #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
01638 # that does not seem to be worth the pain.
01639 # Ask me (Duesentrieb) about it if it's ever needed.
01640 $output = array();
01641 if ( wfIsWindows() ) {
01642 exec( "$command", $output, $exitCode );
01643 } else {
01644 exec( "$command 2>&1", $output, $exitCode );
01645 }
01646
01647 # map exit code to AV_xxx constants.
01648 $mappedCode = $exitCode;
01649 if ( $exitCodeMap ) {
01650 if ( isset( $exitCodeMap[$exitCode] ) ) {
01651 $mappedCode = $exitCodeMap[$exitCode];
01652 } elseif ( isset( $exitCodeMap["*"] ) ) {
01653 $mappedCode = $exitCodeMap["*"];
01654 }
01655 }
01656
01657 if ( $mappedCode === AV_SCAN_FAILED ) {
01658 # scan failed (code was mapped to false by $exitCodeMap)
01659 wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" );
01660
01661 if ( $wgAntivirusRequired ) {
01662 return wfMsg('virus-scanfailed', array( $exitCode ) );
01663 } else {
01664 return NULL;
01665 }
01666 } else if ( $mappedCode === AV_SCAN_ABORTED ) {
01667 # scan failed because filetype is unknown (probably imune)
01668 wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" );
01669 return NULL;
01670 } else if ( $mappedCode === AV_NO_VIRUS ) {
01671 # no virus found
01672 wfDebug( __METHOD__.": file passed virus scan.\n" );
01673 return false;
01674 } else {
01675 $output = join( "\n", $output );
01676 $output = trim( $output );
01677
01678 if ( !$output ) {
01679 $output = true; #if there's no output, return true
01680 } elseif ( $msgPattern ) {
01681 $groups = array();
01682 if ( preg_match( $msgPattern, $output, $groups ) ) {
01683 if ( $groups[1] ) {
01684 $output = $groups[1];
01685 }
01686 }
01687 }
01688
01689 wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output \n" );
01690 return $output;
01691 }
01692 }
01693
01702 function checkMacBinary() {
01703 $macbin = new MacBinary( $this->mTempPath );
01704 if( $macbin->isValid() ) {
01705 $dataFile = tempnam( wfTempDir(), "WikiMacBinary" );
01706 $dataHandle = fopen( $dataFile, 'wb' );
01707
01708 wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" );
01709 $macbin->extractData( $dataHandle );
01710
01711 $this->mTempPath = $dataFile;
01712 $this->mFileSize = $macbin->dataForkLength();
01713
01714 // We'll have to manually remove the new file if it's not kept.
01715 $this->mRemoveTempFile = true;
01716 }
01717 $macbin->close();
01718 }
01719
01725 function cleanupTempFile() {
01726 if ( $this->mRemoveTempFile && $this->mTempPath && file_exists( $this->mTempPath ) ) {
01727 wfDebug( "SpecialUpload::cleanupTempFile: Removing temporary file {$this->mTempPath}\n" );
01728 unlink( $this->mTempPath );
01729 }
01730 }
01731
01739 function checkOverwrite( $name ) {
01740 $img = wfFindFile( $name );
01741
01742 $error = '';
01743 if( $img ) {
01744 global $wgUser, $wgOut;
01745 if( $img->isLocal() ) {
01746 if( !self::userCanReUpload( $wgUser, $img->name ) ) {
01747 $error = 'fileexists-forbidden';
01748 }
01749 } else {
01750 if( !$wgUser->isAllowed( 'reupload' ) ||
01751 !$wgUser->isAllowed( 'reupload-shared' ) ) {
01752 $error = "fileexists-shared-forbidden";
01753 }
01754 }
01755 }
01756
01757 if( $error ) {
01758 $errorText = wfMsg( $error, wfEscapeWikiText( $img->getName() ) );
01759 return $errorText;
01760 }
01761
01762 // Rockin', go ahead and upload
01763 return true;
01764 }
01765
01773 public static function userCanReUpload( User $user, $img ) {
01774 if( $user->isAllowed( 'reupload' ) )
01775 return true;
01776 if( !$user->isAllowed( 'reupload-own' ) )
01777 return false;
01778
01779 $dbr = wfGetDB( DB_SLAVE );
01780 $row = $dbr->selectRow('image',
01781 'img_user',
01782 array( 'img_name' => $img )
01783 );
01784 if ( !$row )
01785 return false;
01786
01787 return $user->getId() == $row->img_user;
01788 }
01789
01793 function showError( $description ) {
01794 global $wgOut;
01795 $wgOut->setPageTitle( wfMsg( "internalerror" ) );
01796 $wgOut->setRobotPolicy( "noindex,nofollow" );
01797 $wgOut->setArticleRelated( false );
01798 $wgOut->enableClientCache( false );
01799 $wgOut->addWikiText( $description );
01800 }
01801
01805 static function getInitialPageText( $comment, $license, $copyStatus, $source ) {
01806 global $wgUseCopyrightUpload;
01807 if ( $wgUseCopyrightUpload ) {
01808 if ( $license != '' ) {
01809 $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
01810 }
01811 $pageText = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n" .
01812 '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
01813 "$licensetxt" .
01814 '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
01815 } else {
01816 if ( $license != '' ) {
01817 $filedesc = $comment == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n";
01818 $pageText = $filedesc .
01819 '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
01820 } else {
01821 $pageText = $comment;
01822 }
01823 }
01824 return $pageText;
01825 }
01826
01834 private function showDeletionLog( $out, $filename ) {
01835 global $wgUser;
01836 $loglist = new LogEventsList( $wgUser->getSkin(), $out );
01837 $pager = new LogPager( $loglist, 'delete', false, $filename );
01838 if( $pager->getNumRows() > 0 ) {
01839 $out->addHTML( '<div class="mw-warning-with-logexcerpt">' );
01840 $out->addWikiMsg( 'upload-wasdeleted' );
01841 $out->addHTML(
01842 $loglist->beginLogEventsList() .
01843 $pager->getBody() .
01844 $loglist->endLogEventsList()
01845 );
01846 $out->addHTML( '</div>' );
01847 }
01848 }
01849 }