* ...various...
* (bug 498) The Views heading in MonoBook.php is now localizable
+* (bug 898) The wiki can now do advanced sanity check on uploaded files
+ including virus checks using external programs.
* (bug 2067) Fixed crash on empty quoted HTML attribute
* (bug 2079) Removed links to Special:Maintenance from movepagetext messages
* Fix for reading incorrectly re-gzipped HistoryBlob entries
$wgUploadBaseUrl = "";
/**#@-*/
+/** internal name of virus scanner. This servers as a key to the $wgAntivirusSetup array.
+ * Set this to NULL to disable virus scanning. If not null, every file uploaded will be scanned for viruses.
+ * @global string $wgAntivirus
+ */
+$wgAntivirus= NULL;
+
+/** Configuration for different virus scanners. This an associative array of associative arrays:
+ * it contains on setup array per known scanner type. The entry is selected by $wgAntivirus, i.e.
+ * valid values for $wgAntivirus are the keys defined in this array.
+ *
+ * The configuration array for each scanner contains the following keys: "command", "codemap", "messagepattern";
+ *
+ * "command" is the full command to call the virus scanner - %f will be replaced with the name of the
+ * file to scan. If not present, the filename will be appended to the command. Note that this must be
+ * overwritten if the scanner is not in the system path; in that case, plase set
+ * $wgAntivirusSetup[$wgAntivirus]['command'] to the desired command with full path.
+ *
+ * "codemap" is a mapping of exit code to return codes of the detectVirus function in SpecialUpload.
+ * An exit code mapped to AV_SCAN_FAILED causes the function to consider the scan to be failed. This will pass
+ * the file if $wgAntivirusRequired is not set.
+ * An exit code mapped to AV_SCAN_ABORTED causes the function to consider the file to have an usupported format,
+ * which is probably imune to virusses. This causes the file to pass.
+ * An exit code mapped to AV_NO_VIRUS will cause the file to pass, meaning no virus was found.
+ * All other codes (like AV_VIRUS_FOUND) will cause the function to report a virus.
+ * You may use "*" as a key in the array to catch all exit codes not mapped otherwise.
+ *
+ * "messagepattern" is a perl regular expression to extract the meaningful part of the scanners
+ * output. The relevant part should be matched as group one (\1).
+ * If not defined or the pattern does not match, the full message is shown to the user.
+ *
+ * @global array $wgAntivirusSetup
+ */
+$wgAntivirusSetup= array(
+
+ #setup for clamav
+ 'clamav' => array (
+ 'command' => "clamscan --no-summary ",
+
+ 'codemap'=> array (
+ "0"=> AV_NO_VIRUS, #no virus
+ "1"=> AV_VIRUS_FOUND, #virus found
+ "52"=> AV_SCAN_ABORTED, #unsupported file format (probably imune)
+ "*"=> AV_SCAN_FAILED, #else scan failed
+ ),
+
+ 'messagepattern'=> '/.*?:(.*)/sim',
+ ),
+
+ #setup for f-prot
+ 'f-prot' => array (
+ 'command' => "f-prot ",
+
+ 'codemap'=> array (
+ "0"=> AV_NO_VIRUS, #no virus
+ "3"=> AV_VIRUS_FOUND, #virus found
+ "6"=> AV_VIRUS_FOUND, #virus found
+ "*"=> AV_SCAN_FAILED, #else scan failed
+ ),
+
+ 'messagepattern'=> '/.*?Infection:(.*)$/m',
+ ),
+);
+
+
+/** Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected.
+ * @global boolean $wgAntivirusRequired
+*/
+$wgAntivirusRequired= true;
+
+/** Determines if the mime type of uploaded files should be checked
+ * @global boolean $wgVerifyMimeType
+*/
+$wgVerifyMimeType= true;
+
+/** Sets the mime type definition file to use by MimeMagic.php.
+* @global string $wgMimeTypeFile
+*/
+#$wgMimeTypeFile= "/etc/mime.types";
+$wgMimeTypeFile= "includes/mime.types";
+#$wgMimeTypeFile= NULL; #use build in defaults only.
+
+/** Sets the mime type info file to use by MimeMagic.php.
+* @global string $wgMimeInfoFile
+*/
+$wgMimeInfoFile= "includes/mime.info";
+#$wgMimeInfoFile= NULL; #use build in defaults only.
+
+/** Switch for loading the FileInfo extension by PECL at runtime.
+* This should be used only if fileinfo is installed as a shared object / dynamic libary
+* @global string $wgLoadFileinfoExtension
+*/
+$wgLoadFileinfoExtension= false;
+
+/** Sets an external mime detector program. The command must print only the mime type to standard output.
+* the name of the file to process will be appended to the command given here.
+* If not set or NULL, mime_content_type will be used if available.
+* @global string $wgMimeTypeFile
+*/
+$wgMimeDetectorCommand= NULL; # use internal mime_content_type function, available since php 4.3.0
+#$wgMimeDetectorCommand= "file -bi" #use external mime detector (linux)
+
+/** Switch for trivial mime detection. Used by thumb.php to disable all fance things,
+* because only a few types of images are needed and file extensions can be trusted.
+*/
+$wgTrivialMimeDetection= false;
+
/**
* Produce hashed HTML article paths. Used internally, do not set.
*/
/** Files with these extensions will never be allowed as uploads. */
$wgFileBlacklist = array(
# HTML may contain cookie-stealing JavaScript and web bugs
- 'html', 'htm',
+ 'html', 'htm', 'js', 'jsb',
# PHP scripts may execute arbitrary code on the server
'php', 'phtml', 'php3', 'php4', 'phps',
# Other types that may be interpreted by some servers
'shtml', 'jhtml', 'pl', 'py', 'cgi',
# May contain harmful executables for Windows victims
'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl' );
+
+/** Files with these mime types will never be allowed as uploads
+ * if $wgVerifyMimeType is enabled.
+ */
+$wgMimeTypeBlacklist= array(
+ # HTML may contain cookie-stealing JavaScript and web bugs
+ 'text/html', 'text/javascript', 'text/x-javascript', 'application/x-shellscript',
+ # PHP scripts may execute arbitrary code on the server
+ 'application/x-php', 'text/x-php',
+ # Other types that may be interpreted by some servers
+ 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh'
+);
/** This is a flag to determine whether or not to check file extensions on upload. */
$wgCheckFileExtensions = true;
* CAUTION: Access to database might lead to code execution
*/
$wgExternalStores = false;
+
+/**
+* list of trusted media-types and mime types.
+* Use the MEDIATYPE_xxx constants to represent media types.
+* This list is used by Image::isSafeFile
+*
+* Types not listed here will have a warning about unsafe content
+* displayed on the images description page. It would also be possible
+* to use this for further restrictions, like disabling direct
+* [[media:...]] links for non-trusted formats.
+*/
+$wgTrustedMediaFormats= array(
+ MEDIATYPE_BITMAP, //all bitmap formats
+ MEDIATYPE_AUDIO, //all audio formats
+ MEDIATYPE_VIDEO, //all plain video formats
+ "image/svg", //svg (only needed if inline rendering of svg is not supported)
+ "application/pdf", //PDF files
+ #"application/x-shockwafe-flash", //flash/shockwave movie
+);
+
?>
define( 'CACHE_ACCEL', 3 ); // eAccelerator or Turck, whichever is available
/**#@-*/
+
+
+/**#@+
+ * Media types.
+ * This defines constants for the value returned by Image::getMediaType()
+ */
+
+define( 'MEDIATYPE_UNKNOWN', 'UNKNOWN' ); // unknown format
+define( 'MEDIATYPE_BITMAP', 'BITMAP' ); // some bitmap image or image source (like psd, etc). Can't scale up.
+define( 'MEDIATYPE_DRAWING', 'DRAWING' ); // some vector drawing (SVG, WMF, PS, ...) or image source (oo-draw, etc). Can scale up.
+define( 'MEDIATYPE_AUDIO', 'AUDIO' ); // simple audio file (ogg, mp3, wav, midi, whatever)
+define( 'MEDIATYPE_VIDEO', 'VIDEO' ); // simple video file (ogg, mpg, etc; no not include formats here that may contain executable sections or scripts!)
+define( 'MEDIATYPE_MULTIMEDIA', 'MULTIMEDIA' ); // Scriptable Multimedia (flash, advanced video container formats, etc)
+define( 'MEDIATYPE_OFFICE', 'OFFICE' ); // Office Documents, Spreadsheets (office formats possibly containing apples, scripts, etc)
+define( 'MEDIATYPE_TEXT', 'TEXT' ); // Plain text (possibly containing program code or scripts)
+define( 'MEDIATYPE_EXECUTABLE', 'EXECUTABLE' ); // binary executable
+define( 'MEDIATYPE_ARCHIVE', 'ARCHIVE' ); // archive file (zip, tar, etc)
+/**#@-*/
+
+/**#@+
+ * Antivirus result codes, for use in $wgAntivirusSetup.
+ */
+
+define( 'AV_NO_VIRUS', 0 ); #scan ok, no virus found
+define( 'AV_VIRUS_FOUND', 1 ); #virus found!
+define( 'AV_SCAN_ABORTED', -1 ); #scan aborted, the file is probably imune
+define( 'AV_SCAN_FAILED', false ); #scan failed (scanner not found or error in scanner)
+
+/**#@-*/
+
?>
return wfElement( $element, $attribs, UtfNormal::cleanUp( $contents ) );
}
+/** Global singleton instance of MimeMagic. This is initialized on demand,
+* please always use the wfGetMimeMagic() function to get the instance.
+*
+* @private
+*/
+$wgMimeMagic= NULL;
+
+/** Factory functions for the global MimeMagic object.
+* This function always returns the same singleton instance of MimeMagic.
+* That objects will be instantiated on the first call to this function.
+* If needed, the MimeMagic.php file is automatically included by this function.
+* @return MimeMagic the global MimeMagic objects.
+*/
+function &wfGetMimeMagic() {
+ global $wgMimeMagic;
+
+ if (!is_null($wgMimeMagic)) {
+ return $wgMimeMagic;
+ }
+
+ if (!class_exists("MimeMagic")) {
+ #include on demand
+ require_once("MimeMagic.php");
+ }
+
+ $wgMimeMagic= new MimeMagic();
+
+ return $wgMimeMagic;
+}
+
+
/**
* Tries to get the system directory for temporary files.
* The TMPDIR, TMP, and TEMP environment variables are checked in sequence,
$width, # \
$height, # |
$bits, # --- returned by getimagesize (loadFromXxx)
- $type, # |
$attr, # /
+ $type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
+ $mime, # MIME type, determined by MimeMagic::guessMimeType
$size, # Size in bytes (loadFromXxx)
$metadata, # Metadata
$exif, # The Exif class
// Check if the key existed and belongs to this version of MediaWiki
if (!empty($cachedValues) && is_array($cachedValues) && isset($cachedValues['width'])
- && $cachedValues['fileExists'] && isset( $cachedValues['metadata'] ) )
+ && $cachedValues['fileExists'] && isset( $cachedValues['mime'] ) && isset( $cachedValues['metadata'] ) )
{
if ( $wgUseSharedUploads && $cachedValues['fromShared']) {
# if this is shared file, we need to check if image
$this->height = $commonsCachedValues['height'];
$this->bits = $commonsCachedValues['bits'];
$this->type = $commonsCachedValues['type'];
+ $this->mime = $commonsCachedValues['mime'];
$this->metadata = $commonsCachedValues['metadata'];
$this->size = $commonsCachedValues['size'];
$this->fromSharedDirectory = true;
$this->height = $cachedValues['height'];
$this->bits = $cachedValues['bits'];
$this->type = $cachedValues['type'];
+ $this->mime = $cachedValues['mime'];
$this->metadata = $cachedValues['metadata'];
$this->size = $cachedValues['size'];
$this->fromSharedDirectory = false;
'height' => $this->height,
'bits' => $this->bits,
'type' => $this->type,
+ 'mime' => $this->mime,
'metadata' => $this->metadata,
'size' => $this->size);
$this->imagePath = $this->getFullPath();
$this->fileExists = file_exists( $this->imagePath );
$this->fromSharedDirectory = false;
- $gis = false;
+ $gis = array();
+
+ if (!$this->fileExists) wfDebug("$fname: ".$this->imagePath." not found locally!\n");
# If the file is not found, and a shared upload directory is used, look for it there.
if (!$this->fileExists && $wgUseSharedUploads && $wgSharedUploadDirectory) {
}
}
+
if ( $this->fileExists ) {
+ $magic=& wfGetMimeMagic();
+
+ $this->mime = $magic->guessMimeType($this->imagePath,true);
+ $this->type = $magic->getMediaType($this->imagePath,$this->mime);
+
# Get size in bytes
$this->size = filesize( $this->imagePath );
+ $magic=& wfGetMimeMagic();
+
# Height and width
- # Don't try to get the width and height of sound and video files, that's bad for performance
- if ( !Image::isKnownImageExtension( $this->extension ) ) {
- $gis = false;
- } elseif( $this->extension == 'svg' ) {
+ if( $this->mime == 'image/svg' ) {
wfSuppressWarnings();
$gis = wfGetSVGsize( $this->imagePath );
wfRestoreWarnings();
- } else {
+ }
+ elseif ( !$magic->isPHPImageType( $this->mime ) ) {
+ # Don't try to get the width and height of sound and video files, that's bad for performance
+ $gis[0]= 0; //width
+ $gis[1]= 0; //height
+ $gis[2]= 0; //unknown
+ $gis[3]= ""; //width height string
+ }
+ else {
wfSuppressWarnings();
$gis = getimagesize( $this->imagePath );
wfRestoreWarnings();
}
+
+ wfDebug("$fname: ".$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n");
}
- if( $gis === false ) {
- $this->width = 0;
- $this->height = 0;
- $this->bits = 0;
- $this->type = 0;
- $this->metadata = serialize ( array() ) ;
- } else {
- $this->width = $gis[0];
- $this->height = $gis[1];
- $this->type = $gis[2];
- $this->metadata = serialize ( $this->retrieveExifData() ) ;
- if ( isset( $gis['bits'] ) ) {
- $this->bits = $gis['bits'];
- } else {
- $this->bits = 0;
- }
+ else {
+ $gis[0]= 0; //width
+ $gis[1]= 0; //height
+ $gis[2]= 0; //unknown
+ $gis[3]= ""; //width height string
+
+ $this->mime = NULL;
+ $this->type = MEDIATYPE_UNKNOWN;
+ wfDebug("$fname: ".$this->imagePath." NOT FOUND!\n");
}
+
+ $this->width = $gis[0];
+ $this->height = $gis[1];
+
+ #NOTE: $gis[2] contains a code for the image type. This is no longer used.
+
+ #NOTE: we have to set this flag early to avoid load() to be called
+ # be some of the functions below. This may lead to recursion or other bad things!
+ # as ther's only one thread of execution, this should be safe anyway.
$this->dataLoaded = true;
+
+
+ if ($this->fileExists) $this->metadata = serialize ( $this->retrieveExifData() ) ;
+ else $this->metadata = serialize ( array() ) ;
+
+ if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
+ else $this->bits = 0;
+
wfProfileOut( $fname );
}
wfProfileIn( $fname );
$dbr =& wfGetDB( DB_SLAVE );
+
+ $this->checkDBSchema($dbr);
+
$row = $dbr->selectRow( 'image',
- array( 'img_size', 'img_width', 'img_height', 'img_bits', 'img_type' , 'img_metadata' ),
+ array( 'img_size', 'img_width', 'img_height', 'img_bits',
+ 'img_media_type', 'img_major_mime', 'img_minor_mime', 'img_metadata' ),
array( 'img_name' => $this->name ), $fname );
if ( $row ) {
$this->fromSharedDirectory = false;
$this->loadFromRow( $row );
$this->imagePath = $this->getFullPath();
// Check for rows from a previous schema, quietly upgrade them
- if ( $this->type == -1 ) {
+ if ( is_null($this->type) ) {
$this->upgradeRow();
}
} elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) {
$name = $wgLang->ucfirst($this->name);
$row = $dbr->selectRow( "`$wgSharedUploadDBname`.image",
- array( 'img_size', 'img_width', 'img_height', 'img_bits', 'img_type' ),
+ array( 'img_size', 'img_width', 'img_height', 'img_bits', 'img_media_type', 'img_major_mime', 'img_minor_mime' ),
array( 'img_name' => $name ), $fname );
if ( $row ) {
$this->fromSharedDirectory = true;
$this->loadFromRow( $row );
// Check for rows from a previous schema, quietly upgrade them
- if ( $this->type == -1 ) {
+ if ( is_null($this->type) ) {
$this->upgradeRow();
}
}
$this->width = $row->img_width;
$this->height = $row->img_height;
$this->bits = $row->img_bits;
- $this->type = $row->img_type;
+ $this->type = $row->img_media_type;
+
+ $major= $row->img_major_mime;
+ $minor= $row->img_minor_mime;
+
+ if (!$major) $this->mime = "unknown/unknown";
+ else {
+ if (!$minor) $minor= "unknown";
+ $this->mime = $major.'/'.$minor;
+ }
+
$this->metadata = $row->img_metadata;
if ( $this->metadata == "" ) $this->metadata = serialize ( array() ) ;
+
$this->dataLoaded = true;
}
// This avoids breaking replication in MySQL
$dbw->selectDB( $wgSharedUploadDBname );
}
+
+ $this->checkDBSchema($dbw);
+
+ if (strpos($this->mime,'/')!==false) {
+ list($major,$minor)= explode('/',$this->mime,2);
+ }
+ else {
+ $major= $this->mime;
+ $minor= "unknown";
+ }
+
+ wfDebug("$fname: upgrading ".$this->name." to 1.5 schema\n");
+
$dbw->update( 'image',
array(
'img_width' => $this->width,
'img_height' => $this->height,
'img_bits' => $this->bits,
- 'img_type' => $this->type,
+ 'img_media_type' => $this->type,
+ 'img_major_mime' => $major,
+ 'img_minor_mime' => $minor,
'img_metadata' => $this->metadata,
), array( 'img_name' => $this->name ), $fname
);
}
function getViewURL() {
- if( $this->mustRender() ) {
- return $this->createThumb( $this->getWidth() );
+ if( $this->mustRender()) {
+ if( $this->canRender() ) {
+ return $this->createThumb( $this->getWidth() );
+ }
+ else {
+ wfDebug('Image::getViewURL(): supposed to render '.$this->name.' ('.$this->mime."), but can't!\n");
+ return $this->getURL(); #hm... return NULL?
+ }
} else {
return $this->getURL();
}
}
/**
- * Return the type of the image
- *
- * - 1 GIF
- * - 2 JPG
- * - 3 PNG
- * - 15 WBMP
- * - 16 XBM
+ * Returns the mime type of the file.
+ */
+ function getMimeType() {
+ $this->load();
+ return $this->mime;
+ }
+
+ /**
+ * Return the type of the media in the file.
+ * Use the value returned by this function with the MEDIATYPE_xxx constants.
*/
- function getType() {
+ function getMediaType() {
$this->load();
return $this->type;
}
+ /**
+ * Checks if the file can be presented to the browser as a bitmap.
+ *
+ * Currently, this checks if the file is an image format
+ * that can be converted to a format
+ * supported by all browsers (namely GIF, PNG and JPEG),
+ * or if it is an SVG image and SVG conversion is enabled.
+ *
+ * @todo remember the result of this check.
+ */
+ function canRender() {
+ global $wgUseImageMagick;
+
+ if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false;
+
+ $mime= $this->getMimeType();
+
+ if (!$mime || $mime==='unknown' || $mime==='unknown/unknown') return false;
+
+ #if it's SVG, check if ther's a converter enabled
+ if ($mime === 'image/svg') {
+ global $wgSVGConverters, $wgSVGConverter;
+
+ if ($wgSVGConverter && isset( $wgSVGConverters[$wgSVGConverter])) {
+ return true;
+ }
+ }
+
+ #image formats available on ALL browsers
+ if ( $mime === 'image/gif'
+ || $mime === 'image/png'
+ || $mime === 'image/jpeg' ) return true;
+
+ #image formats that can be converted to the above formats
+ if ($wgUseImageMagick) {
+ #convertable by ImageMagick (there are more...)
+ if ( $mime === 'image/vnd.wap.wbmp'
+ || $mime === 'image/x-xbitmap'
+ || $mime === 'image/x-xpixmap'
+ #|| $mime === 'image/x-icon' #file may be split into multiple parts
+ || $mime === 'image/x-portable-anymap'
+ || $mime === 'image/x-portable-bitmap'
+ || $mime === 'image/x-portable-graymap'
+ || $mime === 'image/x-portable-pixmap'
+ #|| $mime === 'image/x-photoshop' #this takes a lot of CPU and RAM!
+ || $mime === 'image/x-rgb'
+ || $mime === 'image/x-bmp'
+ || $mime === 'image/tiff' ) return true;
+ }
+ else {
+ #convertable by the PHP GD image lib
+ if ( $mime === 'image/vnd.wap.wbmp'
+ || $mime === 'image/x-xbitmap' ) return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Return true if the file is of a type that can't be directly
+ * rendered by typical browsers and needs to be re-rasterized.
+ *
+ * This returns true for everything but the bitmap types
+ * supported by all browsers, i.e. JPEG; GIF and PNG. It will
+ * also return true for any non-image formats.
+ *
+ * @return bool
+ */
+ function mustRender() {
+ $mime= $this->getMimeType();
+
+ if ( $mime === "image/gif"
+ || $mime === "image/png"
+ || $mime === "image/jpeg" ) return false;
+
+ return true;
+ }
+
+ /**
+ * Determines if this media file may be shown inline on a page.
+ *
+ * This is currently synonymous to canRender(), but this could be
+ * extended to also allow inline display of other media,
+ * like flash animations or videos. If you do so, please keep in mind that
+ * that could be a scurity risc.
+ */
+ function allowInlineDisplay() {
+ return $this->canRender();
+ }
+
+ /**
+ * Determines if this media file is in a format that is unlikely to contain viruses
+ * or malicious content. It uses the global $wgTrustedMediaFormats list to determine
+ * if the file is safe.
+ *
+ * This is used to show a warning on the description page of non-safe files.
+ * It may also be used to disallow direct [[media:...]] links to such files.
+ *
+ * Note that this function will always return ture if allowInlineDisplay()
+ * or isTrustedFile() is true for this file.
+ */
+ function isSafeFile() {
+ if ($this->allowInlineDisplay()) return true;
+ if ($this->isTrustedFile()) return true;
+
+ global $wgTrustedMediaFormats;
+
+ $type= $this->getMediaType();
+ $mime= $this->getMimeType();
+ #wfDebug("Image::isSafeFile: type= $type, mime= $mime\n");
+
+ if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted
+ if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
+
+ if ($mime==="unknown/unknown") return false; #unknown type, not trusted
+ if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
+
+ return false;
+ }
+
+ /** Returns true if the file is flagegd as trusted. Files flagged that way can be
+ * linked to directly, even if that is not allowed for this type of file normally.
+ *
+ * This is a dummy function right now and always returns false. It could be implemented
+ * to extract a flag from the database. The trusted flag could be set on upload, if the
+ * user has sufficient privileges, to bypass script- and html-filters. It may even be
+ * coupeled with cryptographics signatures or such.
+ */
+ function isTrustedFile() {
+ #this could be implemented to check a flag in the databas,
+ #look for signatures, etc
+ return false;
+ }
+
/**
* Return the escapeLocalURL of this image
* @access public
}
if ( $script ) {
$url = $script . '?f=' . urlencode( $this->name ) . '&w=' . urlencode( $width );
+ if( $this->mustRender() ) {
+ $url.= '&r=1';
+ }
} else {
$name = $this->thumbName( $width );
if($this->fromSharedDirectory) {
*/
function thumbName( $width ) {
$thumb = $width."px-".$this->name;
- if( $this->extension == 'svg' ) {
- # Rasterize SVG vector images to PNG
- $thumb .= '.png';
+
+ if( $this->mustRender() ) {
+ if( $this->canRender() ) {
+ # Rasterize to PNG (for SVG vector images, etc)
+ $thumb .= '.png';
+ }
+ else {
+ #should we use iconThumb here to get a symbolic thumbnail?
+ #or should we fail with an internal error?
+ return NULL; //can't make bitmap
+ }
}
return $thumb;
}
return $this->renderThumb( $width );
}
$this->load();
- if ( $width < $this->width ) {
- $thumbheight = $this->height * $width / $this->width;
- $thumbwidth = $width;
- } else {
- $thumbheight = $this->height;
- $thumbwidth = $this->width;
- }
- if ( $thumbheight > $height ) {
- $thumbwidth = $thumbwidth * $height / $thumbheight;
- $thumbheight = $height;
+
+ if ($this->canRender()) {
+ if ( $width < $this->width ) {
+ $thumbheight = $this->height * $width / $this->width;
+ $thumbwidth = $width;
+ } else {
+ $thumbheight = $this->height;
+ $thumbwidth = $this->width;
+ }
+ if ( $thumbheight > $height ) {
+ $thumbwidth = $thumbwidth * $height / $thumbheight;
+ $thumbheight = $height;
+ }
+
+ $thumb = $this->renderThumb( $thumbwidth );
}
- $thumb = $this->renderThumb( $thumbwidth );
+ else $thumb= NULL; #not a bitmap or renderable image, don't try.
+
if( is_null( $thumb ) ) {
$thumb = $this->iconThumb();
}
}
# Sanity check $width
- if( $width <= 0 ) {
+ if( $width <= 0 || $this->width <= 0) {
# BZZZT
return null;
}
}
}
}
+
return new ThumbnailImage( $url, $width, $height, $thumbPath );
} // END OF function renderThumb
/**
* Really render a thumbnail
+ * Call this only for images for which canRender() returns true.
*
* @access private
*/
$this->load();
- if( $this->extension == 'svg' ) {
+ if( $this->mime === "image/svg" ) {
+ #Right now we have only SVG
+
global $wgSVGConverters, $wgSVGConverter;
if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
global $wgSVGConverterPath;
array( '$path/', '$width', '$input', '$output' ),
array( $wgSVGConverterPath,
$width,
- escapeshellarg( $this->imagePath ),
- escapeshellarg( $thumbPath ) ),
+ wfEscapeShellArg( $this->imagePath ),
+ wfEscapeShellArg( $thumbPath ) ),
$wgSVGConverters[$wgSVGConverter] );
$conv = shell_exec( $cmd );
} else {
# in Internet Explorer/Windows instead of default black.
$cmd = $wgImageMagickConvertCommand .
" -quality 85 -background white -geometry {$width} ".
- escapeshellarg($this->imagePath) . " " .
- escapeshellarg($thumbPath);
+ wfEscapeShellArg($this->imagePath) . " " .
+ wfEscapeShellArg($thumbPath);
+ wfDebug("reallyRenderThumb: running ImageMagick: $cmd");
$conv = shell_exec( $cmd );
} else {
# Use PHP's builtin GD library functions.
imagedestroy( $dst_image );
imagedestroy( $src_image );
}
+
#
# Check for zero-sized thumbnails. Those can be generated when
# no disk space is available or some other error occurs
wfPurgeSquidServers( $urls );
}
}
+
+ function checkDBSchema(&$db) {
+ # img_name must be unique
+ if ( !$db->indexUnique( 'image', 'img_name' ) && !$db->indexExists('image','PRIMARY') ) {
+ wfDebugDieBacktrace( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' );
+ }
+
+ #new fields must exist
+ if ( !$db->fieldExists( 'image', 'img_media_type' )
+ || !$db->fieldExists( 'image', 'img_metadata' )
+ || !$db->fieldExists( 'image', 'img_width' ) ) {
+
+ wfDebugDieBacktrace( 'Database schema not up to date, please run maintenance/updater.php' );
+ }
+ }
/**
* Return the image history of this image, line by line.
function nextHistoryLine() {
$fname = 'Image::nextHistoryLine()';
$dbr =& wfGetDB( DB_SLAVE );
+
+ $this->checkDBSchema($dbr);
+
if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
$this->historyRes = $dbr->select( 'image',
array( 'img_size','img_description','img_user','img_user_text','img_timestamp', "'' AS oi_archive_name" ),
function resetHistory() {
$this->historyLine = 0;
}
-
- /**
- * Return true if the file is of a type that can't be directly
- * rendered by typical browsers and needs to be re-rasterized.
- * @return bool
- */
- function mustRender() {
- $this->load();
- return ( $this->extension == 'svg' );
- }
/**
* Return the full filesystem path to the file. Note that this does
return $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
}
- /**
- * @return bool
- * @static
- */
- function isKnownImageExtension( $ext ) {
- static $extensions = array( 'svg', 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'xbm' );
- return in_array( $ext, $extensions );
- }
-
/**
* Record an image upload in the upload log and the image table
*/
$fname = 'Image::recordUpload';
$dbw =& wfGetDB( DB_MASTER );
- # img_name must be unique
- if ( !$dbw->indexUnique( 'image', 'img_name' ) && !$dbw->indexExists('image','PRIMARY') ) {
- wfDebugDieBacktrace( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' );
- }
+ $this->checkDBSchema($dbw);
// Delete thumbnails and refresh the metadata cache
$this->purgeCache();
// Fail now if the image isn't there
if ( !$this->fileExists || $this->fromSharedDirectory ) {
+ wfDebug( "Image::recordUpload: File ".$this->imagePath." went missing!\n" );
return false;
}
$now = $dbw->timestamp();
+ #split mime type
+ if (strpos($this->mime,'/')!==false) {
+ list($major,$minor)= explode('/',$this->mime,2);
+ }
+ else {
+ $major= $this->mime;
+ $minor= "unknown";
+ }
+
# Test to see if the row exists using INSERT IGNORE
# This avoids race conditions by locking the row until the commit, and also
# doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
'img_width' => $this->width,
'img_height' => $this->height,
'img_bits' => $this->bits,
- 'img_type' => $this->type,
+ 'img_media_type' => $this->type,
+ 'img_major_mime' => $major,
+ 'img_minor_mime' => $minor,
'img_timestamp' => $now,
'img_description' => $desc,
'img_user' => $wgUser->getID(),
'oi_width' => 'img_width',
'oi_height' => 'img_height',
'oi_bits' => 'img_bits',
- 'oi_type' => 'img_type',
'oi_timestamp' => 'img_timestamp',
'oi_description' => 'img_description',
'oi_user' => 'img_user',
'oi_user_text' => 'img_user_text',
), array( 'img_name' => $this->name ), $fname
);
-
+
# Update the current image row
$dbw->update( 'image',
array( /* SET */
'img_width' => $this->width,
'img_height' => $this->height,
'img_bits' => $this->bits,
- 'img_type' => $this->type,
+ 'img_media_type' => $this->type,
+ 'img_major_mime' => $major,
+ 'img_minor_mime' => $minor,
'img_timestamp' => $now,
+ 'img_description' => $desc,
'img_user' => $wgUser->getID(),
'img_user_text' => $wgUser->getName(),
- 'img_description' => $desc,
'img_metadata' => $this->metadata,
), array( /* WHERE */
'img_name' => $this->name
* @return array
*/
function retrieveExifData () {
- if ( $this->type !== '2' ) return array ();
+ if ( $this->getMimeType() !== "image/jpeg" ) return array ();
$exif = exif_read_data( $this->imagePath );
foreach($exif as $k => $v) {
# Update EXIF data in database
$dbw =& wfGetDB( DB_MASTER );
+
+ $this->checkDBSchema($dbw);
+
$dbw->update( 'image',
array( 'img_metadata' => $this->metadata ),
array( 'img_name' => $this->name ),
return array_key_exists( $name, $titleList );
}
-
+
/**
$sk = $wgUser->getSkin();
if ( $this->img->exists() ) {
- if ( $this->img->getType() ) {
+ # image
+ $width = $this->img->getWidth();
+ $height = $this->img->getHeight();
+ $showLink = false;
+
+ if ( $this->img->allowInlineDisplay() and $width and $height) {
# image
- $width = $this->img->getWidth();
- $height = $this->img->getHeight();
+
# "Download high res version" link below the image
$msg = wfMsg('showbigimage', $width, $height, intval( $this->img->getSize()/1024 ) );
if ( $width > $maxWidth ) {
$width = floor( $width * $maxHeight / $height );
$height = $maxHeight;
}
- if ( $width != $this->img->getWidth() || $height != $this->img->getHeight() ) {
+ if ( !$this->img->mustRender()
+ && ( $width != $this->img->getWidth() || $height != $this->img->getHeight() ) ) {
if( $wgUseImageResize ) {
$thumbnail = $this->img->getThumbnail( $width );
$url = $thumbnail->getUrl();
$anchorclose = "</a><br />\n$anchoropen{$msg}</a>";
} else {
$url = $full_url;
+ $showLink = $this->img->mustRender();
}
- $s = '<div class="fullImageLink" id="file">' . $anchoropen .
+ $wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen .
"<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" .
- htmlspecialchars( $wgRequest->getVal( 'image' ) ).'" />' . $anchorclose . '</div>';
+ htmlspecialchars( $wgRequest->getVal( 'image' ) ).'" />' . $anchorclose . '</div>' );
} else {
- $s = "<div class=\"fullMedia\">" . $sk->makeMediaLink( $this->img->getName(),'' ) . '</div>';
+ #if direct link is allowed but it's not a renderable image, show an icon.
+ if ($this->img->isSafeFile()) {
+ $icon= $this->img->iconThumb();
+
+ $wgOut->addHTML( '<div class="fullImageLink" id="file"><a href="' . $full_url . '">' .
+ $icon->toHtml() .
+ '</a></div>' );
+ }
+
+ $showLink = true;
}
- $wgOut->addHTML( $s );
+
+
+ if ($showLink) {
+ $s= $sk->makeMediaLink( $this->img->getName(), '', '', true );
+ $info= wfMsg( 'fileinfo', ceil($this->img->getSize()/1024.0), $this->img->getMimeType() );
+
+ if (!$this->img->isSafeFile()) {
+ $wgOut->addHTML("<div class=\"fullMedia\">");
+ $wgOut->addHTML("<span class=\"dangerousLink\">");
+ $wgOut->addHTML($s);
+ $wgOut->addHTML("</span>");
+
+ $wgOut->addHTML("<span class=\"fileInfo\"> (");
+ $wgOut->addWikiText( $info, false );
+ $wgOut->addHTML(")</span>");
+ $wgOut->addHTML("</div>");
+
+ #this should be formated a little nicer. Is CSS sufficient?
+ $wgOut->addHTML("<div class=\"mediaWarning\">");
+ $wgOut->addWikiText( wfMsg( 'mediawarning' ) );
+ $wgOut->addHTML('</div>');
+
+ } else {
+ $wgOut->addHTML("<div class=\"fullMedia\">");
+ $wgOut->addHTML($s);
+
+ $wgOut->addHTML("<span class=\"fileInfo\"> (");
+ $wgOut->addWikiText( $info, false );
+ $wgOut->addHTML(")</span>");
+
+ $wgOut->addHTML("</div>");
+ }
+ }
+
if($this->img->fromSharedDirectory) {
$sharedtext="<div class=\"sharedUploadNotice\">" . wfMsg("sharedupload");
if($wgRepositoryBaseUrl) {
--- /dev/null
+<?php
+/** Module defining helper functions for detecting and dealing with mime types.
+ *
+ * @package MediaWiki
+ */
+
+ /** Defines a set of well known mime types
+ * This is used as a fallback to mime.types files.
+ * An extensive list of well known mime types is provided by
+ * the file mime.types in the includes directory.
+ */
+define('MM_WELL_KNOWN_MIME_TYPES',<<<END_STRING
+application/ogg ogg ogm
+application/pdf pdf
+application/x-javascript js
+application/x-shockwave-flash swf
+audio/midi mid midi kar
+audio/mpeg mpga mpa mp2 mp3
+audio/x-aiff aif aiff aifc
+audio/x-wav wav
+audio/ogg ogg
+image/x-bmp bmp
+image/gif gif
+image/jpeg jpeg jpg jpe
+image/png png
+image/svg+xml svg
+image/tiff tiff tif
+text/plain txt
+text/html html htm
+video/ogg ogm ogg
+video/mpeg mpg mpeg
+END_STRING
+);
+
+ /** Defines a set of well known mime info entries
+ * This is used as a fallback to mime.info files.
+ * An extensive list of well known mime types is provided by
+ * the file mime.info in the includes directory.
+ */
+define('MM_WELL_KNOWN_MIME_INFO', <<<END_STRING
+application/pdf [OFFICE]
+text/javascript application/x-javascript [EXECUTABLE]
+application/x-shockwave-flash [MULTIMEDIA]
+audio/midi [AUDIO]
+audio/x-aiff [AUDIO]
+audio/x-wav [AUDIO]
+audio/mp3 audio/mpeg [AUDIO]
+application/ogg audio/ogg video/ogg [MULTIMEDIA]
+image/x-bmp image/bmp [BITMAP]
+image/gif [BITMAP]
+image/jpeg [BITMAP]
+image/png [BITMAP]
+image/svg image/svg+xml [DRAWING]
+image/tiff [BITMAP]
+text/plain [TEXT]
+text/html [TEXT]
+video/ogg [VIDEO]
+video/mpeg [VIDEO]
+unknown/unknown application/octet-stream application/x-empty [UNKNOWN]
+END_STRING
+);
+
+#note: because this file is possibly included by a function,
+#we need to access the global scope explicitely!
+global $wgLoadFileinfoExtension;
+
+if ($wgLoadFileinfoExtension) {
+ if(!extension_loaded('fileinfo')) dl('fileinfo.' . PHP_SHLIB_SUFFIX);
+}
+
+/** Implements functions related to mime types such as detection and mapping to
+* file extension,
+*
+* Instances of this class are stateles, there only needs to be one global instance
+* of MimeMagic. Please use wfGetMimeMagic to get that instance.
+*/
+class MimeMagic {
+
+ /**
+ * Mapping of media types to arrays of mime types.
+ * This is used by findMediaType and getMediaType, respectively
+ */
+ var $mMediaTypes= NULL;
+
+ /** Map of mime type aliases
+ */
+ var $mMimeTypeAliases= NULL;
+
+ /** map of mime types to file extensions (as a space seprarated list)
+ */
+ var $mMimeToExt= NULL;
+
+ /** map of file extensions types to mime types (as a space seprarated list)
+ */
+ var $mExtToMime= NULL;
+
+ /** Initializes the MimeMagic object. This is called by wfGetMimeMagic when instantiation
+ * the global MimeMagic singleton object.
+ *
+ * This constructor parses the mime.types and mime.info files and build internal mappings.
+ */
+ function MimeMagic() {
+ /*
+ * --- load mime.types ---
+ */
+
+ global $wgMimeTypeFile;
+
+ $types= MM_WELL_KNOWN_MIME_TYPES;
+
+ if ($wgMimeTypeFile) {
+ if (is_file($wgMimeTypeFile) and is_readable($wgMimeTypeFile)) {
+ wfDebug("MimeMagic::MimeMagic: loading mime types from $wgMimeTypeFile\n");
+
+ $types.= "\n";
+ $types.= file_get_contents($wgMimeTypeFile);
+ }
+ else wfDebug("MimeMagic::MimeMagic: can't load mime types from $wgMimeTypeFile\n");
+ }
+ else wfDebug("MimeMagic::MimeMagic: no mime types file defined, using build-ins only.\n");
+
+ $types= str_replace(array("\r\n","\n\r","\n\n","\r\r","\r"),"\n",$types);
+ $types= str_replace("\t"," ",$types);
+
+ $this->mMimeToExt= array();
+ $this->mToMime= array();
+
+ $lines= explode("\n",$types);
+ foreach ($lines as $s) {
+ $s= trim($s);
+ if (empty($s)) continue;
+ if (strpos($s,'#')===0) continue;
+
+ $s= strtolower($s);
+ $i= strpos($s,' ');
+
+ if ($i===false) continue;
+
+ #print "processing MIME line $s<br>";
+
+ $mime= substr($s,0,$i);
+ $ext= trim(substr($s,$i+1));
+
+ if (empty($ext)) continue;
+
+ if (@$this->mMimeToExt[$mime]) $this->mMimeToExt[$mime] .= ' '.$ext;
+ else $this->mMimeToExt[$mime]= $ext;
+
+ $extensions= explode(' ',$ext);
+
+ foreach ($extensions as $e) {
+ $e= trim($e);
+ if (empty($e)) continue;
+
+ if (@$this->mExtToMime[$e]) $this->mExtToMime[$e] .= ' '.$mime;
+ else $this->mExtToMime[$e]= $mime;
+ }
+ }
+
+ /*
+ * --- load mime.info ---
+ */
+
+ global $wgMimeInfoFile;
+
+ $info= MM_WELL_KNOWN_MIME_INFO;
+
+ if ($wgMimeInfoFile) {
+ if (is_file($wgMimeInfoFile) and is_readable($wgMimeInfoFile)) {
+ wfDebug("MimeMagic::MimeMagic: loading mime info from $wgMimeInfoFile\n");
+
+ $info.= "\n";
+ $info.= file_get_contents($wgMimeInfoFile);
+ }
+ else wfDebug("MimeMagic::MimeMagic: can't load mime info from $wgMimeInfoFile\n");
+ }
+ else wfDebug("MimeMagic::MimeMagic: no mime info file defined, using build-ins only.\n");
+
+ $info= str_replace(array("\r\n","\n\r","\n\n","\r\r","\r"),"\n",$info);
+ $info= str_replace("\t"," ",$info);
+
+ $this->mMimeTypeAliases= array();
+ $this->mMediaTypes= array();
+
+ $lines= explode("\n",$info);
+ foreach ($lines as $s) {
+ $s= trim($s);
+ if (empty($s)) continue;
+ if (strpos($s,'#')===0) continue;
+
+ $s= strtolower($s);
+ $i= strpos($s,' ');
+
+ if ($i===false) continue;
+
+ #print "processing MIME INFO line $s<br>";
+
+ $match= array();
+ if (preg_match('!\[\s*(\w+)\s*\]!',$s,$match)) {
+ $s= preg_replace('!\[\s*(\w+)\s*\]!','',$s);
+ $mtype= trim(strtoupper($match[1]));
+ }
+ else $mtype= MEDIATYPE_UNKNOWN;
+
+ $m= explode(' ',$s);
+
+ if (!isset($this->mMediaTypes[$mtype])) $this->mMediaTypes[$mtype]= array();
+
+ foreach ($m as $mime) {
+ $mime= trim($mime);
+ if (empty($mime)) continue;
+
+ $this->mMediaTypes[$mtype][]= $mime;
+ }
+
+ if (sizeof($m)>1) {
+ $main= $m[0];
+ for ($i=1; $i<sizeof($m); $i+= 1) {
+ $mime= $m[$i];
+ $this->mMimeTypeAliases[$mime]= $main;
+ }
+ }
+ }
+
+ }
+
+ /** returns a list of file extensions for a given mime type
+ * as a space separated string.
+ */
+ function getExtensionsForType($mime) {
+ $mime= strtolower($mime);
+
+ $r= @$this->mMimeToExt[$mime];
+
+ if (@!$r and isset($this->mMimeTypeAliases[$mime])) {
+ $mime= $this->mMimeTypeAliases[$mime];
+ $r= @$this->mMimeToExt[$mime];
+ }
+
+ return $r;
+ }
+
+ /** returns a list of mime types for a given file extension
+ * as a space separated string.
+ */
+ function getTypesForExtension($ext) {
+ $ext= strtolower($ext);
+
+ $r= @$this->mExtToMime[$ext];
+ return $r;
+ }
+
+ /** returns a single mime type for a given file extension.
+ * This is always the first type from the list returned by getTypesForExtension($ext).
+ */
+ function guessTypesForExtension($ext) {
+ $m= $this->getTypesForExtension( $ext );
+ if( is_null($m) ) return NULL;
+
+ $m= trim( $m );
+ $m= preg_replace('/\s.*$/','',$m);
+
+ return $m;
+ }
+
+
+ /** tests if the extension matches the given mime type.
+ * returns true if a match was found, NULL if the mime type is unknown,
+ * and false if the mime type is known but no matches where found.
+ */
+ function isMatchingExtension($extension,$mime) {
+ $ext= $this->getExtensionsForType($mime);
+
+ if (!$ext) {
+ return NULL; //unknown
+ }
+
+ $ext= explode(' ',$ext);
+
+ $extension= strtolower($extension);
+ if (in_array($extension,$ext)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /** returns true if the mime type is known to represent
+ * an image format supported by the PHP GD library.
+ */
+ function isPHPImageType( $mime ) {
+ #as defined by imagegetsize and image_type_to_mime
+ static $types = array(
+ 'image/gif', 'image/jpeg', 'image/png',
+ 'image/x-bmp', 'image/xbm', 'image/tiff',
+ 'image/jp2', 'image/jpeg2000', 'image/iff',
+ 'image/xbm', 'image/x-xbitmap',
+ 'image/vnd.wap.wbmp', 'image/vnd.xiff',
+ 'image/x-photoshop',
+ 'application/x-shockwave-flash',
+ );
+
+ return in_array( $mime, $types );
+ }
+
+
+ /** mime type detection. This uses detectMimeType to detect the mim type of the file,
+ * but applies additional checks to determine some well known file formats that may be missed
+ * or misinterpreter by the default mime detection (namely xml based formats like XHTML or SVG).
+ *
+ * @param string $file The file to check
+ * @param bool $useExt switch for allowing to use the file extension to guess the mime type. true by default.
+ *
+ * @return string the mime type of $file
+ */
+ function guessMimeType( $file, $useExt=true ) {
+ $fname = 'MimeMagic::guessMimeType';
+ $mime= $this->detectMimeType($file,$useExt);
+
+ if (strpos($mime,"text/")===0 ||
+ $mime==="application/xml") {
+
+ // Read a chunk of the file
+ $f = fopen( $file, "rt" );
+ if( !$f ) return "unknown/unknown";
+ $head = fread( $f, 1024 );
+ fclose( $f );
+
+ $xml_type= NULL;
+ $script_type= NULL;
+
+ /*
+ * look for XML formats (XHTML and SVG)
+ */
+ if ($mime==="text/sgml" ||
+ $mime==="text/plain" ||
+ $mime==="text/html" ||
+ $mime==="text/xml" ||
+ $mime==="application/xml") {
+
+ if (substr($head,0,5)=="<?xml") $xml_type= "ASCII";
+ elseif (substr($head,0,8)=="\xef\xbb\xbf<?xml") $xml_type= "UTF-8";
+ elseif (substr($head,0,10)=="\xfe\xff\x00<\x00?\x00x\x00m\x00l") $xml_type= "UTF-16BE";
+ elseif (substr($head,0,10)=="\xff\xfe<\x00?\x00x\x00m\x00l\x00") $xml_type= "UTF-16LE";
+
+ if ($xml_type) {
+ if ($xml_type!=="UTF-8" && $xml_type!=="ASCII") $head= iconv($xml_type,"ASCII//IGNORE",$head);
+
+ $match= array();
+ $doctype= "";
+ $tag= "";
+
+ if (preg_match('%<!DOCTYPE\s+[\w-]+\s+PUBLIC\s+["'."'".'"](.*?)["'."'".'"].*>%sim',$head,$match)) $doctype= $match[1];
+ if (preg_match('%<(\w+).*>%sim',$head,$match)) $tag= $match[1];
+
+ #print "<br>ANALYSING $file ($mime): doctype= $doctype; tag= $tag<br>";
+
+ if (strpos($doctype,"-//W3C//DTD SVG")===0) $mime= "image/svg";
+ elseif ($tag==="svg") $mime= "image/svg";
+ elseif (strpos($doctype,"-//W3C//DTD XHTML")===0) $mime= "text/html";
+ elseif ($tag==="html") $mime= "text/html";
+
+ $test_more= false;
+ }
+ }
+
+ /*
+ * look for shell scripts
+ */
+ if (!$xml_type) {
+ $script_type= NULL;
+
+ #detect by shebang
+ if (substr($head,0,2)=="#!") $script_type= "ASCII";
+ elseif (substr($head,0,5)=="\xef\xbb\xbf#!") $script_type= "UTF-8";
+ elseif (substr($head,0,7)=="\xfe\xff\x00#\x00!") $script_type= "UTF-16BE";
+ elseif (substr($head,0,7)=="\xff\xfe#\x00!") $script_type= "UTF-16LE";
+
+ if ($script_type) {
+ if ($script_type!=="UTF-8" && $script_type!=="ASCII") $head= iconv($script_type,"ASCII//IGNORE",$head);
+
+ $match= array();
+ $prog= "";
+
+ if (preg_match('%/?([^\s]+/)(w+)%sim',$head,$match)) $script= $match[2];
+
+ $mime= "application/x-$prog";
+ }
+ }
+
+ /*
+ * look for PHP
+ */
+ if( !$xml_type && !$script_type ) {
+
+ if( ( strpos( $head, '<?php' ) !== false ) ||
+ ( strpos( $head, '<? ' ) !== false ) ||
+ ( strpos( $head, "<?\n" ) !== false ) ||
+ ( strpos( $head, "<?\t" ) !== false ) ||
+ ( strpos( $head, "<?=" ) !== false ) ||
+
+ ( strpos( $head, "<\x00?\x00p\x00h\x00p" ) !== false ) ||
+ ( strpos( $head, "<\x00?\x00 " ) !== false ) ||
+ ( strpos( $head, "<\x00?\x00\n" ) !== false ) ||
+ ( strpos( $head, "<\x00?\x00\t" ) !== false ) ||
+ ( strpos( $head, "<\x00?\x00=" ) !== false ) ) {
+
+ $mime= "application/x-php";
+ }
+ }
+
+ }
+
+ if (isset($this->mMimeTypeAliases[$mime])) $mime= $this->mMimeTypeAliases[$mime];
+
+ wfDebug("$fname: final mime type of $file: $mime\n");
+ return $mime;
+ }
+
+ /** Internal mime type detection, please use guessMimeType() for application code instead.
+ * Detection is done using an external program, if $wgMimeDetectorCommand is set.
+ * Otherwise, the fileinfo extension and mime_content_type are tried (in this order), if they are available.
+ * If the dections fails and $useExt is true, the mime type is guessed from the file extension, using guessTypesForExtension.
+ * If the mime type is still unknown, getimagesize is used to detect the mime type if the file is an image.
+ * If no mime type can be determined, this function returns "unknown/unknown".
+ *
+ * @param string $file The file to check
+ * @param bool $useExt switch for allowing to use the file extension to guess the mime type. true by default.
+ *
+ * @return string the mime type of $file
+ * @private
+ */
+ function detectMimeType( $file, $useExt=true ) {
+ $fname = 'MimeMagic::detectMimeType';
+
+ global $wgMimeDetectorCommand;
+
+ $m= NULL;
+ if ($wgMimeDetectorCommand) {
+ $fn= wfEscapeShellArg($file);
+ $m= `$wgMimeDetectorCommand $fn`;
+ }
+ else if (function_exists("finfo_open") && function_exists("finfo_file")) {
+
+ # This required the fileinfo extension by PECL,
+ # see http://pecl.php.net/package/fileinfo
+ # This must be compiled into PHP
+ #
+ # finfo is the official replacement for the deprecated
+ # mime_content_type function, see below.
+ #
+ # If you may need to load the fileinfo extension at runtime, set
+ # $wgLoadFileinfoExtension in LocalSettings.php
+
+ $mime_magic_resource = finfo_open(FILEINFO_MIME); /* return mime type ala mimetype extension */
+
+ if ($mime_magic_resource) {
+ $m= finfo_file($mime_magic_resource, $file);
+
+ finfo_close($mime_magic_resource);
+ }
+ else wfDebug("$fname: finfo_open failed on ".FILEINFO_MIME."!\n");
+ }
+ else if (function_exists("mime_content_type")) {
+
+ # NOTE: this function is available since PHP 4.3.0, but only if
+ # PHP was compiled with --with-mime-magic or, before 4.3.2, with --enable-mime-magic.
+ #
+ # On Winodws, you must set mime_magic.magicfile in php.ini to point to the mime.magic file bundeled with PHP;
+ # sometimes, this may even be needed under linus/unix.
+ #
+ # Also note that this has been DEPRECATED in favor of the fileinfo extension by PECL, see above.
+ # see http://www.php.net/manual/en/ref.mime-magic.php for details.
+
+ $m= mime_content_type($file);
+ }
+ else wfDebug("$fname: no magic mime detector found!\n");
+
+ if ($m) {
+ #normalize
+ $m= preg_replace('![;, ].*$!','',$m); #strip charset, etc
+ $m= trim($m);
+ $m= strtolower($m);
+
+ if (strpos($m,'unknown')!==false) $m= NULL;
+ else {
+ wfDebug("$fname: magic mime type of $file: $m\n");
+ return $m;
+ }
+ }
+
+ #if still not known, use getimagesize to find out the type of image
+ #TODO: skip things that do not have a well-known image extension? Would that be safe?
+ wfSuppressWarnings();
+ $gis = getimagesize( $file );
+ wfRestoreWarnings();
+
+ $notAnImage= false;
+
+ if ($gis && is_array($gis) && $gis[2]) {
+ switch ($gis[2]) {
+ case IMAGETYPE_GIF: $m= "image/gif"; break;
+ case IMAGETYPE_JPEG: $m= "image/jpeg"; break;
+ case IMAGETYPE_PNG: $m= "image/png"; break;
+ case IMAGETYPE_SWF: $m= "application/x-shockwave-flash"; break;
+ case IMAGETYPE_PSD: $m= "application/photoshop"; break;
+ case IMAGETYPE_BMP: $m= "image/bmp"; break;
+ case IMAGETYPE_TIFF_II: $m= "image/tiff"; break;
+ case IMAGETYPE_TIFF_MM: $m= "image/tiff"; break;
+ case IMAGETYPE_JPC: $m= "image"; break;
+ case IMAGETYPE_JP2: $m= "image/jpeg2000"; break;
+ case IMAGETYPE_JPX: $m= "image/jpeg2000"; break;
+ case IMAGETYPE_JB2: $m= "image"; break;
+ case IMAGETYPE_SWC: $m= "application/x-shockwave-flash"; break;
+ case IMAGETYPE_IFF: $m= "image/vnd.xiff"; break;
+ case IMAGETYPE_WBMP: $m= "image/vnd.wap.wbmp"; break;
+ case IMAGETYPE_XBM: $m= "image/x-xbitmap"; break;
+ }
+
+ if ($m) {
+ wfDebug("$fname: image mime type of $file: $m\n");
+ return $m;
+ }
+ else $notAnImage= true;
+ }
+
+ #if desired, look at extension as a fallback.
+ if ($useExt) {
+ $i = strrpos( $file, '.' );
+ $e= strtolower( $i ? substr( $file, $i + 1 ) : '' );
+
+ $m= $this->guessTypesForExtension($e);
+
+ #TODO: if $notAnImage is set, do not trust the file extension if
+ # the results is one of the image types that should have been recognized
+ # by getimagesize
+
+ if ($m) {
+ wfDebug("$fname: extension mime type of $file: $m\n");
+ return $m;
+ }
+ }
+
+ #unknown type
+ wfDebug("$fname: failed to guess mime type for $file!\n");
+ return "unknown/unknown";
+ }
+
+ /**
+ * Determine the media type code for a file, using its mime type, name and possibly
+ * its contents.
+ *
+ * This function relies on the findMediaType(), mapping extensions and mime
+ * types to media types.
+ *
+ * @todo analyse file if need be
+ * @todo look at multiple extension, separately and together.
+ *
+ * @param string $path full path to the image file, in case we have to look at the contents
+ * (if null, only the mime type is used to determine the media type code).
+ * @param string $mime mime type. If null it will be guessed using guessMimeType.
+ *
+ * @return (int?string?) a value to be used with the MEDIATYPE_xxx constants.
+ */
+ function getMediaType($path=NULL,$mime=NULL) {
+ if( !$mime && !$path ) return MEDIATYPE_UNKNOWN;
+
+ #if mime type is unknown, guess it
+ if( !$mime ) $mime= $this->guessMimeType($path,false);
+
+ #special code for ogg - detect if it's video (theora),
+ #else label it as sound.
+ if( $mime=="application/ogg" && file_exists($path) ) {
+
+ // Read a chunk of the file
+ $f = fopen( $path, "rt" );
+ if( !$f ) return MEDIATYPE_UNKNOWN;
+ $head = fread( $f, 256 );
+ fclose( $f );
+
+ $head= strtolower( $head );
+
+ #This is an UGLY HACK, file should be parsed correctly
+ if( strpos($head,'theora')!==false ) return MEDIATYPE_VIDEO;
+ elseif( strpos($head,'vorbis')!==false ) return MEDIATYPE_AUDIO;
+ elseif( strpos($head,'flac')!==false ) return MEDIATYPE_AUDIO;
+ elseif( strpos($head,'speex')!==false ) return MEDIATYPE_AUDIO;
+ else return MEDIATYPE_MULTIMEDIA;
+ }
+
+ #check for entry for full mime type
+ if( $mime ) {
+ $type= $this->findMediaType($mime);
+ if( $type!==MEDIATYPE_UNKNOWN ) return $type;
+ }
+
+ #check for entry for file extension
+ $e= NULL;
+ if( $path ) {
+ $i = strrpos( $path, '.' );
+ $e= strtolower( $i ? substr( $path, $i + 1 ) : '' );
+
+ #TODO: look at multi-extension if this fails, parse from full path
+
+ $type= $this->findMediaType('.'.$e);
+ if( $type!==MEDIATYPE_UNKNOWN ) return $type;
+ }
+
+ #check major mime type
+ if( $mime ) {
+ $i= strpos($mime,'/');
+ if( $i !== false ) {
+ $major= substr($mime,0,$i);
+ $type= $this->findMediaType($major);
+ if( $type!==MEDIATYPE_UNKNOWN ) return $type;
+ }
+ }
+
+ if( !$type ) $type= MEDIATYPE_UNKNOWN;
+
+ return $type;
+ }
+
+ /** returns a media code matching the given mime type or file extension.
+ * File extensions are represented by a string starting with a dot (.) to
+ * distinguish them from mime types.
+ *
+ * This funktion relies on the mapping defined by $this->mMediaTypes
+ * @private
+ */
+ function findMediaType($extMime) {
+
+ if (strpos($extMime,'.')===0) { #if it's an extension, look up the mime types
+ $m= $this->getTypesForExtension(substr($extMime,1));
+ if (!$m) return MEDIATYPE_UNKNOWN;
+
+ $m= explode(' ',$m);
+ }
+ else { #normalize mime type
+ if (isset($this->mMimeTypeAliases[$extMime])) {
+ $extMime= $this->mMimeTypeAliases[$extMime];
+ }
+
+ $m= array($extMime);
+ }
+
+ foreach ($m as $mime) {
+ foreach ($this->mMediaTypes as $type => $codes) {
+ if (in_array($mime,$codes,true)) return $type;
+ }
+ }
+
+ return MEDIATYPE_UNKNOWN;
+ }
+}
+
+?>
return $this->uploadError( wfMsg( 'protectedpage' ) );
}
- /* Don't allow users to override the blacklist */
+ /* Don't allow users to override the blacklist (check file extension) */
global $wgStrictFileExtensions;
global $wgFileExtensions, $wgFileBlacklist;
if( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) ||
* type but it's corrupt or data of the wrong type, we should
* probably not accept it.
*/
- if( !$this->mStashed && !$this->verify( $this->mUploadTempName, $finalExt ) ) {
- return $this->uploadError( wfMsg( 'uploadcorrupt' ) );
+ if( !$this->mStashed ) {
+ $veri= $this->verify($this->mUploadTempName, $finalExt);
+
+ if( $veri !== true ) { //it's a wiki error...
+ return $this->uploadError( $veri->toString() );
+ }
}
/**
function saveUploadedFile( $saveName, $tempName, $useRename = false ) {
global $wgUploadDirectory, $wgOut;
+ $fname= "SpecialUpload::saveUploadedFile";
+
$dest = wfImageDir( $saveName );
$archive = wfImageArchiveDir( $saveName );
$this->mSavedFile = "{$dest}/{$saveName}";
"${archive}/{$this->mUploadOldVersion}" );
return false;
}
- } else {
+ else wfDebug("$fname: moved file ".$this->mSavedFile." to ${archive}/{$this->mUploadOldVersion}\n");
+ }
+ else {
$this->mUploadOldVersion = '';
}
if( ! $success ) {
$wgOut->fileCopyError( $tempName, $this->mSavedFile );
return false;
+ } else {
+ wfDebug("$fname: wrote tempfile $tempName to ".$this->mSavedFile."\n");
}
} else {
wfSuppressWarnings();
$wgOut->fileCopyError( $tempName, $this->mSavedFile );
return false;
}
+ else wfDebug("$fname: wrote tempfile $tempName to ".$this->mSavedFile."\n");
}
+
chmod( $this->mSavedFile, 0644 );
return true;
}
}
/**
- * Returns false if the file is of a known type but can't be recognized,
- * indicating a corrupt file.
- * Returns true otherwise; unknown file types are not checked if given
- * with an unrecognized extension.
+ * Verifies that it's ok to include the uploaded file
*
- * @param string $tmpfile Pathname to the temporary upload file
+ * @param string $tmpfile the full path opf the temporary file to verify
* @param string $extension The filename extension that the file is to be served with
- * @return bool
+ * @return mixed true of the file is verified, a WikiError object otherwise.
*/
function verify( $tmpfile, $extension ) {
- if( $this->triggersIEbug( $tmpfile ) ||
- $this->triggersSafariBug( $tmpfile ) ) {
- return false;
- }
+ #magically determine mime type
+ $magic=& wfGetMimeMagic();
+ $mime= $magic->guessMimeType($tmpfile,false);
- $fname = 'SpecialUpload::verify';
- $mergeExtensions = array(
- 'jpg' => 'jpeg',
- 'tif' => 'tiff' );
- $extensionTypes = array(
- # See http://www.php.net/getimagesize
- 1 => 'gif',
- 2 => 'jpeg',
- 3 => 'png',
- 4 => 'swf',
- 5 => 'psd',
- 6 => 'bmp',
- 7 => 'tiff',
- 8 => 'tiff',
- 9 => 'jpc',
- 10 => 'jp2',
- 11 => 'jpx',
- 12 => 'jb2',
- 13 => 'swc',
- 14 => 'iff',
- 15 => 'wbmp',
- 16 => 'xbm' );
-
- $extension = strtolower( $extension );
- if( isset( $mergeExtensions[$extension] ) ) {
- $extension = $mergeExtensions[$extension];
- }
- wfDebug( "$fname: Testing file '$tmpfile' with given extension '$extension'\n" );
-
- if( !in_array( $extension, $extensionTypes ) ) {
- # Not a recognized image type. We don't know how to verify these.
- # They're allowed by policy or they wouldn't get this far, so we'll
- # let them slide for now.
- wfDebug( "$fname: Unknown extension; passing.\n" );
- return true;
- }
+ $fname= "SpecialUpload::verify";
- wfSuppressWarnings();
- $data = getimagesize( $tmpfile );
- wfRestoreWarnings();
- if( false === $data ) {
- # Didn't recognize the image type.
- # Either the image is corrupt or someone's slipping us some
- # bogus data such as HTML+JavaScript trying to take advantage
- # of an Internet Explorer security flaw.
- wfDebug( "$fname: getimagesize() doesn't recognize the file; rejecting.\n" );
- return false;
- }
+ #check mime type, if desired
+ global $wgVerifyMimeType;
+ if ($wgVerifyMimeType) {
+
+ #check mime type against file extension
+ if( !$this->verifyExtension( $mime, $extension ) ) {
+ return new WikiErrorMsg( 'uploadcorrupt' );
+ }
- $imageType = $data[2];
- if( !isset( $extensionTypes[$imageType] ) ) {
- # Now we're kind of confused. Perhaps new image types added
- # to PHP's support that we don't know about.
- # We'll let these slide for now.
- wfDebug( "$fname: getimagesize() knows the file, but we don't recognize the type; passing.\n" );
- return true;
+ #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' );
}
- $ext = strtolower( $extension );
- if( $extension != $extensionTypes[$imageType] ) {
- # The given filename extension doesn't match the
- # file type. Probably just a mistake, but it's a stupid
- # one and we shouldn't let it pass. KILL THEM!
- wfDebug( "$fname: file extension does not match recognized type; rejecting.\n" );
- return false;
+ /**
+ * Scan the uploaded file for viruses
+ */
+ $virus= $this->detectVirus($tmpfile);
+ if ( $virus ) {
+ return new WikiErrorMsg( 'uploadvirus', htmlspecialchars($virus) );
}
wfDebug( "$fname: all clear; passing.\n" );
}
/**
- * Internet Explorer for Windows performs some really stupid file type
- * autodetection which can cause it to interpret valid image files as HTML
- * and potentially execute JavaScript, creating a cross-site scripting
- * attack vectors.
+ * Checks if the mime type of the uploaded file matches the file extension.
*
- * Returns true if IE is likely to mistake the given file for HTML.
- *
- * @param string $filename
+ * @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 triggersIEbug( $filename ) {
- $file = fopen( $filename, 'rb' );
- $chunk = strtolower( fread( $file, 256 ) );
- fclose( $file );
+ function verifyExtension( $mime, $extension ) {
+ $fname = 'SpecialUpload::verifyExtension';
+
+ if (!$mime || $mime=="unknown" || $mime=="unknown/unknown") {
+ wfDebug( "$fname: passing file with unknown mime type\n" );
+ return true;
+ }
+
+ $magic=& wfGetMimeMagic();
+
+ $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("<!DOCTYPE *X?HTML",$chunk)) return true;
+
+ /**
+ * Internet Explorer for Windows performs some really stupid file type
+ * autodetection which can cause it to interpret valid image files as HTML
+ * and potentially execute JavaScript, creating a cross-site scripting
+ * attack vectors.
+ *
+ * Apple's Safari browser also performs some unsafe file type autodetection
+ * which can cause legitimate files to be interpreted as HTML if the
+ * web server is not correctly configured to send the right content-type
+ * (or if you're really uploading plain text and octet streams!)
+ *
+ * Returns true if IE is likely to mistake the given file for HTML.
+ * Also returns true if Safari would mistake the given file for HTML
+ * when served with a generic content-type.
+ */
$tags = array(
'<body',
'<head',
- '<html',
+ '<html', #also in safari
'<img',
'<pre',
- '<script',
+ '<script', #also in safari
'<table',
- '<title' );
+ '<title' #also in safari
+ );
+
foreach( $tags as $tag ) {
if( false !== strpos( $chunk, $tag ) ) {
return true;
}
}
+
+ /*
+ * look for javascript
+ */
+
+ #resolve entity-refs to look at attributes. may be harsh on big files... cache result?
+ $chunk= wfMungeToUtf8($chunk); #this should actually use do_html_decode_entites, once this also deals with numeric entities.
+
+ #look for script-types
+ if (preg_match("!type\s*=\s*['\"]?\s*(\w*/)?(ecma|java)!sim",$chunk)) return true;
+
+ #look for html-style script-urls
+ if (preg_match("!(href|src|data)\s*=\s*['\"]?\s*(ecma|java)script:!sim",$chunk)) return true;
+
+ #look for css-style script-urls
+ if (preg_match("!url\s*\(\s*['\"]?\s*(ecma|java)script:!sim",$chunk)) return true;
+
+ wfDebug("SpecialUpload::detectScript: no scripts found\n");
return false;
}
+
+ /** 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");
- /**
- * Apple's Safari browser performs some unsafe file type autodetection
- * which can cause legitimate files to be interpreted as HTML if the
- * web server is not correctly configured to send the right content-type
- * (or if you're really uploading plain text and octet streams!)
- *
- * Returns true if Safari would mistake the given file for HTML
- * when served with a generic content-type.
- *
- * @param string $filename
- * @return bool
- */
- function triggersSafariBug( $filename ) {
- $file = fopen( $filename, 'rb' );
- $chunk = strtolower( fread( $file, 1024 ) );
- fclose( $file );
+ $wgOut->addHTML( "<div class='error'>Bad configuration: unknown virus scanner: <i>$wgAntivirus</i></div>\n" ); #LOCALIZE
+
+ return "unknown antivirus: $wgAntivirus";
+ }
- $tags = array(
- '<html',
- '<script',
- '<title' );
- foreach( $tags as $tag ) {
- if( false !== strpos( $chunk, $tag ) ) {
- return true;
+ #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;
}
- return false;
}
+
}
?>
}
}
+ header( 'Content-Length: ' . $stat['size'] );
+
$type = wfGetType( $fname );
- if ( $type ) {
+ if ( $type and $type!="unknown/unknown") {
header("Content-type: $type");
} else {
header('Content-type: application/x-wiki');
}
+
readfile( $fname );
}
function wfGetType( $filename ) {
- # There's probably a better way to do this
- $types = <<<END_STRING
-application/andrew-inset ez
-application/mac-binhex40 hqx
-application/mac-compactpro cpt
-application/mathml+xml mathml
-application/msword doc
-application/octet-stream bin dms lha lzh exe class so dll
-application/oda oda
-application/ogg ogg
-application/pdf pdf
-application/postscript ai eps ps
-application/rdf+xml rdf
-application/smil smi smil
-application/srgs gram
-application/srgs+xml grxml
-application/vnd.mif mif
-application/vnd.ms-excel xls
-application/vnd.ms-powerpoint ppt
-application/vnd.wap.wbxml wbxml
-application/vnd.wap.wmlc wmlc
-application/vnd.wap.wmlscriptc wmlsc
-application/voicexml+xml vxml
-application/x-bcpio bcpio
-application/x-cdlink vcd
-application/x-chess-pgn pgn
-application/x-cpio cpio
-application/x-csh csh
-application/x-director dcr dir dxr
-application/x-dvi dvi
-application/x-futuresplash spl
-application/x-gtar gtar
-application/x-hdf hdf
-application/x-javascript js
-application/x-koan skp skd skt skm
-application/x-latex latex
-application/x-netcdf nc cdf
-application/x-sh sh
-application/x-shar shar
-application/x-shockwave-flash swf
-application/x-stuffit sit
-application/x-sv4cpio sv4cpio
-application/x-sv4crc sv4crc
-application/x-tar tar
-application/x-tcl tcl
-application/x-tex tex
-application/x-texinfo texinfo texi
-application/x-troff t tr roff
-application/x-troff-man man
-application/x-troff-me me
-application/x-troff-ms ms
-application/x-ustar ustar
-application/x-wais-source src
-application/xhtml+xml xhtml xht
-application/xslt+xml xslt
-application/xml xml xsl
-application/xml-dtd dtd
-application/zip zip
-audio/basic au snd
-audio/midi mid midi kar
-audio/mpeg mpga mp2 mp3
-audio/x-aiff aif aiff aifc
-audio/x-mpegurl m3u
-audio/x-pn-realaudio ram rm
-audio/x-pn-realaudio-plugin rpm
-audio/x-realaudio ra
-audio/x-wav wav
-chemical/x-pdb pdb
-chemical/x-xyz xyz
-image/bmp bmp
-image/cgm cgm
-image/gif gif
-image/ief ief
-image/jpeg jpeg jpg jpe
-image/png png
-image/svg+xml svg
-image/tiff tiff tif
-image/vnd.djvu djvu djv
-image/vnd.wap.wbmp wbmp
-image/x-cmu-raster ras
-image/x-icon ico
-image/x-portable-anymap pnm
-image/x-portable-bitmap pbm
-image/x-portable-graymap pgm
-image/x-portable-pixmap ppm
-image/x-rgb rgb
-image/x-xbitmap xbm
-image/x-xpixmap xpm
-image/x-xwindowdump xwd
-model/iges igs iges
-model/mesh msh mesh silo
-model/vrml wrl vrml
-text/calendar ics ifb
-text/css css
-text/richtext rtx
-text/rtf rtf
-text/sgml sgml sgm
-text/tab-separated-values tsv
-text/vnd.wap.wml wml
-text/vnd.wap.wmlscript wmls
-text/x-setext etx
-video/mpeg mpeg mpg mpe
-video/quicktime qt mov
-video/vnd.mpegurl mxu
-video/x-msvideo avi
-video/x-sgi-movie movie
-x-conference/x-cooltalk ice
-END_STRING;
- // Needed for windows servers who use \r\n not \n
- $endl = "
-";
- $types = explode( $endl, $types );
- if ( !preg_match( "/\.([^.]*?)$/", $filename, $matches ) ) {
- return false;
- }
+ global $wgTrivialMimeDetection;
- foreach( $types as $type ) {
- $extensions = explode( " ", $type );
- for ( $i=1; $i<count( $extensions ); $i++ ) {
- if ( $extensions[$i] == $matches[1] ) {
- return $extensions[0];
- }
+ # trivial detection by file extension,
+ # used for thumbnails (thumb.php)
+ if ($wgTrivialMimeDetection) {
+ $ext= strtolower(strrchr($filename, '.'));
+
+ switch ($ext) {
+ case '.gif': return "image/gif";
+ case '.png': return "image/png";
+ case '.jpg': return "image/jpeg";
+ case '.jpeg': return "image/jpeg";
}
+
+ return "unknown/unknown";
+ }
+ else {
+ $magic=& wfGetMimeMagic();
+ return $magic->guessMimeType($filename); //full fancy mime detection
}
- return false;
}
?>
--- /dev/null
+#Mime type info file.
+#the first mime type in each line is the "main" mime type,
+#the others are aliases for this type
+#the media type is given in upper case and square brackets,
+#like [BITMAP], and must indicate a media type as defined by
+#the MEDIATYPE_xxx constants in Defines.php
+
+
+image/gif [BITMAP]
+image/png [BITMAP]
+image/ief [BITMAP]
+image/jpeg [BITMAP]
+image/xbm [BITMAP]
+image/tiff [BITMAP]
+image/x-icon [BITMAP]
+image/x-rgb [BITMAP]
+image/x-portable-pixmap [BITMAP]
+image/x-portable-graymap image/x-portable-greymap [BITMAP]
+image/x-bmp image/bmp application/x-bmp application/bmp [BITMAP]
+image/x-photoshop image/psd image/x-psd image/photoshop [BITMAP]
+
+image/svg image/svg+xml application/svg+xml application/svg [DRAWING]
+application/postscript [DRAWING]
+application/x-latex [DRAWING]
+application/x-tex [DRAWING]
+
+
+audio/mp3 audio/mpeg3 audio/mpeg [AUDIO]
+audio/wav audio/x-wav audio/wave [AUDIO]
+audio/mid audio/midi [AUDIO]
+audio/basic [AUDIO]
+audio/x-aiff [AUDIO]
+audio/x-pn-realaudio [AUDIO]
+audio/x-realaudio [AUDIO]
+
+video/mpeg application/mpeg [VIDEO]
+video/ogg [VIDEO]
+video/x-sgi-video [VIDEO]
+
+application/ogg application/x-ogg audio/ogg audio/x-ogg video/ogg video/x-ogg [MULTIMEDIA]
+
+application/x-shockwave-flash [MULTIMEDIA]
+audio/x-pn-realaudio-plugin [MULTIMEDIA]
+model/iges [MULTIMEDIA]
+model/mesh [MULTIMEDIA]
+model/vrml [MULTIMEDIA]
+video/quicktime [MULTIMEDIA]
+video/x-msvideo [MULTIMEDIA]
+
+text/plain [TEXT]
+text/html application/xhtml+xml [TEXT]
+application/xml text/xml [TEXT]
+text [TEXT]
+
+application/zip application/x-zip [ARCHIVE]
+application/x-gzip [ARCHIVE]
+application/x-bzip [ARCHIVE]
+application/x-tar [ARCHIVE]
+application/x-stuffit [ARCHIVE]
+
+
+text/javascript application/x-javascript application/x-ecmascript text/ecmascript [EXECUTABLE]
+application/x-bash [EXECUTABLE]
+application/x-sh [EXECUTABLE]
+application/x-csh [EXECUTABLE]
+application/x-tcsh [EXECUTABLE]
+application/x-tcl [EXECUTABLE]
+application/x-perl [EXECUTABLE]
+application/x-python [EXECUTABLE]
+
+application/pdf application/acrobat [OFFICE]
+application/msword [OFFICE]
+application/vnd.ms-excel [OFFICE]
+application/vnd.ms-powerpoint [OFFICE]
+application/x-director [OFFICE]
+text/rtf [OFFICE]
--- /dev/null
+application/andrew-inset ez
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/mathml+xml mathml
+application/msword doc
+application/octet-stream bin dms lha lzh exe class so dll
+application/oda oda
+application/ogg ogg ogm
+application/pdf pdf
+application/postscript ai eps ps
+application/rdf+xml rdf
+application/smil smi smil
+application/srgs gram
+application/srgs+xml grxml
+application/vnd.mif mif
+application/vnd.ms-excel xls
+application/vnd.ms-powerpoint ppt
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/voicexml+xml vxml
+application/x-bcpio bcpio
+application/x-bzip gz bz2
+application/x-cdlink vcd
+application/x-chess-pgn pgn
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar tar
+application/x-gzip gz
+application/x-hdf hdf
+application/x-jar jar
+application/x-javascript js
+application/x-koan skp skd skt skm
+application/x-latex latex
+application/x-netcdf nc cdf
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-troff t tr roff
+application/x-troff-man man
+application/x-troff-me me
+application/x-troff-ms ms
+application/x-ustar ustar
+application/x-wais-source src
+application/x-xpinstall xpi
+application/xhtml+xml xhtml xht
+application/xslt+xml xslt
+application/xml xml xsl
+application/xml-dtd dtd
+application/zip zip jar xpi sxc stc sxd std sxi sti sxm stm sxw stw
+audio/basic au snd
+audio/midi mid midi kar
+audio/mpeg mpga mp2 mp3
+audio/ogg ogg
+audio/x-aiff aif aiff aifc
+audio/x-mpegurl m3u
+audio/x-ogg ogg
+audio/x-pn-realaudio ram rm
+audio/x-pn-realaudio-plugin rpm
+audio/x-realaudio ra
+audio/x-wav wav
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm cgm
+image/gif gif
+image/ief ief
+image/jpeg jpeg jpg jpe
+image/png png
+image/svg+xml svg
+image/tiff tiff tif
+image/vnd.djvu djvu djv
+image/vnd.wap.wbmp wbmp
+image/x-cmu-raster ras
+image/x-icon ico
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-rgb rgb
+image/x-photoshop psd
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+model/iges igs iges
+model/mesh msh mesh silo
+model/vrml wrl vrml
+text/calendar ics ifb
+text/css css
+text/html html htm
+text/plain txt
+text/richtext rtx
+text/rtf rtf
+text/sgml sgml sgm
+text/tab-separated-values tsv
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/xml xml xsl xslt rss rdf
+text/x-setext etx
+video/mpeg mpeg mpg mpe
+video/ogg ogm ogg
+video/quicktime qt mov
+video/vnd.mpegurl mxu
+video/x-msvideo avi
+video/x-ogg ogm ogg
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice
\ No newline at end of file
--- /dev/null
+-- media type columns, added for 1.5
+-- this alters the scheme for 1.5, img_type is no longer used.
+
+ALTER TABLE /*$wgDBprefix*/image ADD (
+ -- Media type as defined by the MEDIATYPE_xxx constants
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+
+ -- major part of a MIME media type as defined by IANA
+ -- see http://www.iana.org/assignments/media-types/
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+
+ -- minor part of a MIME media type as defined by IANA
+ -- the minor parts are not required to adher to any standard
+ -- but should be consistent throughout the database
+ -- see http://www.iana.org/assignments/media-types/
+ img_minor_mime varchar(32) NOT NULL default "unknown"
+);
+
+-- img_type is no longer used, delete it
+ALTER TABLE /*$wgDBprefix*/image DROP COLUMN img_type;
\ No newline at end of file
-- Extra image metadata, added for 1.5
+-- NOTE: as by patch-img_media_type.sql, the img_type
+-- column is no longer used and has therefore be removed from this patch
+
ALTER TABLE /*$wgDBprefix*/image ADD (
img_width int(5) NOT NULL default 0,
img_height int(5) NOT NULL default 0,
img_bits int(5) NOT NULL default 0,
- img_type int(5) NOT NULL default -1
+ -- img_type int(5) NOT NULL default -1
);
ALTER TABLE /*$wgDBprefix*/oldimage ADD (
oi_width int(5) NOT NULL default 0,
oi_height int(5) NOT NULL default 0,
oi_bits int(3) NOT NULL default 0,
- oi_type int(3) NOT NULL default 0
+ -- oi_type int(3) NOT NULL default 0
);
'img_width' => 1941,
'img_height' => 220,
'img_bits' => 24,
- 'img_type' => 2, // 2 == JPEG
+ 'img_media_type' => MEDIATYPE_BITMAP,
+ 'img_major_mime' => "image",
+ 'img_minor_mime' => "jpeg",
) );
$setupDB = true;
-- For images, bits per pixel if known.
img_bits int(3) NOT NULL default '0',
- -- File type key returned by getimagesize().
- -- See http://www.php.net/getimagesize for possible values.
- img_type int(3) NOT NULL default '0',
+ -- Media type as defined by the MEDIATYPE_xxx constants
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+
+ -- major part of a MIME media type as defined by IANA
+ -- see http://www.iana.org/assignments/media-types/
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+
+ -- minor part of a MIME media type as defined by IANA
+ -- the minor parts are not required to adher to any standard
+ -- but should be consistent throughout the database
+ -- see http://www.iana.org/assignments/media-types/
+ img_minor_mime varchar(32) NOT NULL default "unknown",
-- Description field as entered by the uploader.
-- This is displayed in image upload history and logs.
oi_width int(5) NOT NULL default 0,
oi_height int(5) NOT NULL default 0,
oi_bits int(3) NOT NULL default 0,
- oi_type int(3) NOT NULL default 0,
oi_description tinyblob NOT NULL default '',
oi_user int(5) unsigned NOT NULL default '0',
oi_user_text varchar(255) binary NOT NULL default '',
array( 'revision', 'rev_deleted', 'patch-rev_deleted.sql' ),
array( 'image', 'img_width', 'patch-img_width.sql' ),
array( 'image', 'img_metadata', 'patch-img_metadata.sql' ),
+ array( 'image', 'img_media_type', 'patch-img_media_type.sql' ),
);
function rename_table( $from, $to, $patch ) {
require_once( './includes/Defines.php' );
require_once( './LocalSettings.php' );
require_once( 'GlobalFunctions.php' );
+
+$wgTrivialMimeDetection = true; //don't use fancy mime detection, just check the file extension for jpg/gif/png.
+
require_once( 'Image.php' );
require_once( 'StreamFile.php' );
$width = $_REQUEST['w'];
}
+$pre_render= isset($_REQUEST['r']) && $_REQUEST['r']!="0";
+
// Some basic input validation
$width = intval( $width );
$imagePath = wfImageDir( $fileName ) . '/' . $fileName;
$thumbName = "{$width}px-$fileName";
-if ( preg_match( '/\.svg$/', $fileName ) ) {
+if ( $pre_render ) {
$thumbName .= '.png';
}
$thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;