' );
+ $sk = $wgUser->getSkin();
+
+
+ $sourcefilename = wfMsgHtml( 'sourcefilename' );
+ $destfilename = wfMsgHtml( 'destfilename' );
+ $summary = wfMsgWikiHtml( 'fileuploadsummary' );
+
+ $licenses = new Licenses();
+ $license = wfMsgHtml( 'license' );
+ $nolicense = wfMsgHtml( 'nolicense' );
+ $licenseshtml = $licenses->getHtml();
+
+ $ulb = wfMsgHtml( 'uploadbtn' );
+
+
+ $titleObj = Title::makeTitle( NS_SPECIAL, 'Upload' );
+ $action = $titleObj->escapeLocalURL();
+
+ $encDestFile = htmlspecialchars( $this->mDestFile );
+
+ $watchChecked = $wgUser->getOption( 'watchdefault' )
+ ? 'checked="checked"'
+ : '';
+
+ $wgOut->addHTML( "
+ " );
+ }
+
+ /* -------------------------------------------------------------- */
+
+ /**
+ * Split a file into a base name and all dot-delimited 'extensions'
+ * on the end. Some web server configurations will fall back to
+ * earlier pseudo-'extensions' to determine type and execute
+ * scripts, so the blacklist needs to check them all.
+ *
+ * @return array
+ */
+ function splitExtensions( $filename ) {
+ $bits = explode( '.', $filename );
+ $basename = array_shift( $bits );
+ return array( $basename, $bits );
+ }
+
+ /**
+ * Perform case-insensitive match against a list of file extensions.
+ * Returns true if the extension is in the list.
+ *
+ * @param string $ext
+ * @param array $list
+ * @return bool
+ */
+ function checkFileExtension( $ext, $list ) {
+ return in_array( strtolower( $ext ), $list );
+ }
+
+ /**
+ * Perform case-insensitive match against a list of file extensions.
+ * Returns true if any of the extensions are in the list.
+ *
+ * @param array $ext
+ * @param array $list
+ * @return bool
+ */
+ function checkFileExtensionList( $ext, $list ) {
+ foreach( $ext as $e ) {
+ if( in_array( strtolower( $e ), $list ) ) {
+ return true;
+ }
}
- } else {
- $wgUploadOldVersion = "";
+ return false;
}
- if ( ! move_uploaded_file( $wpUploadTempName, $wgSavedFile ) ) {
- $wgOut->fileCopyError( $wpUploadTempName, $wgSavedFile );
+
+ /**
+ * Verifies that it's ok to include the uploaded file
+ *
+ * @param string $tmpfile the full path of the temporary file to verify
+ * @param string $extension The filename extension that the file is to be served with
+ * @return mixed true of the file is verified, a WikiError object otherwise.
+ */
+ function verify( $tmpfile, $extension ) {
+ #magically determine mime type
+ $magic=& wfGetMimeMagic();
+ $mime= $magic->guessMimeType($tmpfile,false);
+
+ $fname= "SpecialUpload::verify";
+
+ #check mime type, if desired
+ global $wgVerifyMimeType;
+ if ($wgVerifyMimeType) {
+
+ #check mime type against file extension
+ if( !$this->verifyExtension( $mime, $extension ) ) {
+ return new WikiErrorMsg( 'uploadcorrupt' );
+ }
+
+ #check mime type blacklist
+ global $wgMimeTypeBlacklist;
+ if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist)
+ && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
+ return new WikiErrorMsg( 'badfiletype', htmlspecialchars( $mime ) );
+ }
+ }
+
+ #check for htmlish code and javascript
+ if( $this->detectScript ( $tmpfile, $mime ) ) {
+ return new WikiErrorMsg( 'uploadscripted' );
+ }
+
+ /**
+ * Scan the uploaded file for viruses
+ */
+ $virus= $this->detectVirus($tmpfile);
+ if ( $virus ) {
+ return new WikiErrorMsg( 'uploadvirus', htmlspecialchars($virus) );
+ }
+
+ wfDebug( "$fname: all clear; passing.\n" );
+ return true;
}
- chmod( $wgSavedFile, 0644 );
-}
-function unsaveUploadedFile()
-{
- global $wpSessionKey, $wpUploadOldVersion;
- global $wgUploadDirectory, $wgOut, $wsUploadFiles;
-
- $wgSavedFile = $wsUploadFiles[$wpSessionKey];
- $wgUploadOldVersion = $wpUploadOldVersion;
-
- if ( ! @unlink( $wgSavedFile ) ) {
- $wgOut->fileDeleteError( $wgSavedFile );
- return;
+ /**
+ * Checks if the mime type of the uploaded file matches the file extension.
+ *
+ * @param string $mime the mime type of the uploaded file
+ * @param string $extension The filename extension that the file is to be served with
+ * @return bool
+ */
+ function verifyExtension( $mime, $extension ) {
+ $fname = 'SpecialUpload::verifyExtension';
+
+ $magic =& wfGetMimeMagic();
+
+ if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
+ if ( ! $magic->isRecognizableExtension( $extension ) ) {
+ wfDebug( "$fname: passing file with unknown detected mime type; unrecognized extension '$extension', can't verify\n" );
+ return true;
+ } else {
+ wfDebug( "$fname: rejecting file with unknown detected mime type; recognized extension '$extension', so probably invalid file\n" );
+ return false;
+ }
+
+ $match= $magic->isMatchingExtension($extension,$mime);
+
+ if ($match===NULL) {
+ wfDebug( "$fname: no file extension known for mime type $mime, passing file\n" );
+ return true;
+ } elseif ($match===true) {
+ wfDebug( "$fname: mime type $mime matches extension $extension, passing file\n" );
+
+ #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
+ return true;
+
+ } else {
+ wfDebug( "$fname: mime type $mime mismatches file extension $extension, rejecting file\n" );
+ return false;
+ }
+ }
+
+ /** Heuristig for detecting files that *could* contain JavaScript instructions or
+ * things that may look like HTML to a browser and are thus
+ * potentially harmful. The present implementation will produce false positives in some situations.
+ *
+ * @param string $file Pathname to the temporary upload file
+ * @param string $mime The mime type of the file
+ * @return bool true if the file contains something looking like embedded scripts
+ */
+ function detectScript($file,$mime) {
+
+ #ugly hack: for text files, always look at the entire file.
+ #For binarie field, just check the first K.
+
+ if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file );
+ else {
+ $fp = fopen( $file, 'rb' );
+ $chunk = fread( $fp, 1024 );
+ fclose( $fp );
+ }
+
+ $chunk= strtolower( $chunk );
+
+ if (!$chunk) return false;
+
+ #decode from UTF-16 if needed (could be used for obfuscation).
+ if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE";
+ elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE";
+ else $enc= NULL;
+
+ if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk);
+
+ $chunk= trim($chunk);
+
+ #FIXME: convert from UTF-16 if necessarry!
+
+ wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n");
+
+ #check for HTML doctype
+ if (eregi("fileRenameError( "{$archive}/{$wgUploadOldVersion}",
- $wgSavedFile );
+ /** Generic wrapper function for a virus scanner program.
+ * This relies on the $wgAntivirus and $wgAntivirusSetup variables.
+ * $wgAntivirusRequired may be used to deny upload if the scan fails.
+ *
+ * @param string $file Pathname to the temporary upload file
+ * @return mixed false if not virus is found, NULL if the scan fails or is disabled,
+ * or a string containing feedback from the virus scanner if a virus was found.
+ * If textual feedback is missing but a virus was found, this function returns true.
+ */
+ function detectVirus($file) {
+ global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired;
+
+ $fname= "SpecialUpload::detectVirus";
+
+ if (!$wgAntivirus) { #disabled?
+ wfDebug("$fname: virus scanner disabled\n");
+
+ return NULL;
+ }
+
+ if (!$wgAntivirusSetup[$wgAntivirus]) {
+ wfDebug("$fname: unknown virus scanner: $wgAntivirus\n");
+
+ $wgOut->addHTML( "
Bad configuration: unknown virus scanner: $wgAntivirus
\n" ); #LOCALIZE
+
+ return "unknown antivirus: $wgAntivirus";
+ }
+
+ #look up scanner configuration
+ $virus_scanner= $wgAntivirusSetup[$wgAntivirus]["command"]; #command pattern
+ $virus_scanner_codes= $wgAntivirusSetup[$wgAntivirus]["codemap"]; #exit-code map
+ $msg_pattern= $wgAntivirusSetup[$wgAntivirus]["messagepattern"]; #message pattern
+
+ $scanner= $virus_scanner; #copy, so we can resolve the pattern
+
+ if (strpos($scanner,"%f")===false) $scanner.= " ".wfEscapeShellArg($file); #simple pattern: append file to scan
+ else $scanner= str_replace("%f",wfEscapeShellArg($file),$scanner); #complex pattern: replace "%f" with file to scan
+
+ wfDebug("$fname: running virus scan: $scanner \n");
+
+ #execute virus scanner
+ $code= false;
+
+ #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
+ # that does not seem to be worth the pain.
+ # Ask me (Duesentrieb) about it if it's ever needed.
+ if (wfIsWindows()) exec("$scanner",$output,$code);
+ else exec("$scanner 2>&1",$output,$code);
+
+ $exit_code= $code; #remeber for user feedback
+
+ if ($virus_scanner_codes) { #map exit code to AV_xxx constants.
+ if (isset($virus_scanner_codes[$code])) $code= $virus_scanner_codes[$code]; #explicite mapping
+ else if (isset($virus_scanner_codes["*"])) $code= $virus_scanner_codes["*"]; #fallback mapping
+ }
+
+ if ($code===AV_SCAN_FAILED) { #scan failed (code was mapped to false by $virus_scanner_codes)
+ wfDebug("$fname: failed to scan $file (code $exit_code).\n");
+
+ if ($wgAntivirusRequired) return "scan failed (code $exit_code)";
+ else return NULL;
+ }
+ else if ($code===AV_SCAN_ABORTED) { #scan failed because filetype is unknown (probably imune)
+ wfDebug("$fname: unsupported file type $file (code $exit_code).\n");
+ return NULL;
+ }
+ else if ($code===AV_NO_VIRUS) {
+ wfDebug("$fname: file passed virus scan.\n");
+ return false; #no virus found
+ }
+ else {
+ $output= join("\n",$output);
+ $output= trim($output);
+
+ if (!$output) $output= true; #if ther's no output, return true
+ else if ($msg_pattern) {
+ $groups= array();
+ if (preg_match($msg_pattern,$output,$groups)) {
+ if ($groups[1]) $output= $groups[1];
+ }
+ }
+
+ wfDebug("$fname: FOUND VIRUS! scanner feedback: $output");
+ return $output;
}
}
-}
-function uploadError( $error )
-{
- global $wgOut;
- $sub = wfMsg( "uploadwarning" );
- $wgOut->addHTML( "
{$sub}
\n" );
- $wgOut->addHTML( "
{$error}
\n" );
-}
+ /**
+ * Check if the temporary file is MacBinary-encoded, as some uploads
+ * from Internet Explorer on Mac OS Classic and Mac OS X will be.
+ * If so, the data fork will be extracted to a second temporary file,
+ * which will then be checked for validity and either kept or discarded.
+ *
+ * @access private
+ */
+ function checkMacBinary() {
+ $macbin = new MacBinary( $this->mUploadTempName );
+ if( $macbin->isValid() ) {
+ $dataFile = tempnam( wfTempDir(), "WikiMacBinary" );
+ $dataHandle = fopen( $dataFile, 'wb' );
+
+ wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" );
+ $macbin->extractData( $dataHandle );
+
+ $this->mUploadTempName = $dataFile;
+ $this->mUploadSize = $macbin->dataForkLength();
-function uploadWarning( $warning )
-{
- global $wgOut, $wgUser, $wgLang, $wgUploadDirectory;
- global $wpUpload, $wpReUpload, $wpUploadAffirm, $wpUploadFile;
- global $wpUploadDescription, $wpIgnoreWarning;
- global $wpUploadSaveName, $wpUploadTempName, $wpUploadSize;
- global $wgSavedFile, $wgUploadOldVersion;
- global $wpSessionKey, $wpUploadOldVersion, $wsUploadFiles;
- global $wgUseCopyrightUpload , $wpUploadCopyStatus , $wpUploadSource ;
-
- # wgSavedFile is stored in the session not the form, for security
- $wpSessionKey = mt_rand( 0, 0x7fffffff );
- $wsUploadFiles[$wpSessionKey] = $wgSavedFile;
-
- $sub = wfMsg( "uploadwarning" );
- $wgOut->addHTML( "
{$sub}
\n" );
- $wgOut->addHTML( "
{$warning}
\n" );
-
- $save = wfMsg( "savefile" );
- $reupload = wfMsg( "reupload" );
- $iw = wfMsg( "ignorewarning" );
- $reup = wfMsg( "reuploaddesc" );
- $action = wfLocalUrlE( $wgLang->specialPage( "Upload" ),
- "action=submit" );
-
- if ( $wgUseCopyrightUpload )
- {
- $copyright = "
-
-
-";
+ // We'll have to manually remove the new file if it's not kept.
+ $this->mRemoveTempFile = true;
+ }
+ $macbin->close();
}
- $wgOut->addHTML( "
-\n" );
-}
+ /**
+ * If we've modified the upload file we need to manually remove it
+ * on exit to clean up.
+ * @access private
+ */
+ function cleanupTempFile() {
+ if( $this->mRemoveTempFile && file_exists( $this->mUploadTempName ) ) {
+ wfDebug( "SpecialUpload::cleanupTempFile: Removing temporary file $this->mUploadTempName\n" );
+ unlink( $this->mUploadTempName );
+ }
+ }
+
+ /**
+ * Check if there's an overwrite conflict and, if so, if restrictions
+ * forbid this user from performing the upload.
+ *
+ * @return mixed true on success, WikiError on failure
+ * @access private
+ */
+ function checkOverwrite( $name ) {
+ $img = Image::newFromName( $name );
+ if( is_null( $img ) ) {
+ // Uh... this shouldn't happen ;)
+ // But if it does, fall through to previous behavior
+ return false;
+ }
-function mainUploadForm( $msg )
-{
- global $wgOut, $wgUser, $wgLang, $wgUploadDirectory;
- global $wpUpload, $wpUploadAffirm, $wpUploadFile;
- global $wpUploadDescription, $wpIgnoreWarning;
- global $wgUseCopyrightUpload , $wpUploadSource , $wpUploadCopyStatus ;
-
- if ( "" != $msg ) {
- $sub = wfMsg( "uploaderror" );
- $wgOut->addHTML( "