* (bug 898) Mime type autodetection.
authorÆvar Arnfjörð Bjarmason <avar@users.mediawiki.org>
Sat, 21 May 2005 07:46:17 +0000 (07:46 +0000)
committerÆvar Arnfjörð Bjarmason <avar@users.mediawiki.org>
Sat, 21 May 2005 07:46:17 +0000 (07:46 +0000)
17 files changed:
RELEASE-NOTES
includes/DefaultSettings.php
includes/Defines.php
includes/GlobalFunctions.php
includes/Image.php
includes/ImagePage.php
includes/MimeMagic.php [new file with mode: 0644]
includes/SpecialUpload.php
includes/StreamFile.php
includes/mime.info [new file with mode: 0644]
includes/mime.types [new file with mode: 0644]
maintenance/archives/patch-img_media_type.sql [new file with mode: 0644]
maintenance/archives/patch-img_width.sql
maintenance/parserTests.php
maintenance/tables.sql
maintenance/updaters.inc
thumb.php

index 8ff8b84..b16c241 100644 (file)
@@ -166,6 +166,8 @@ Various bugfixes, small features, and a few experimental things:
 
 * ...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
index d5ab56c..118ff6b 100644 (file)
@@ -110,6 +110,112 @@ $wgTmpDirectory     = "{$wgUploadDirectory}/tmp";
 $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.
  */ 
@@ -795,13 +901,25 @@ $wgFileExtensions = array( 'png', 'gif', 'jpg', 'jpeg' );
 /** 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;
@@ -1314,4 +1432,24 @@ $wgCountCategorizedImagesAsUsed = false;
  * 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 
+);
+
 ?>
index c49b209..6f8b1b9 100644 (file)
@@ -89,4 +89,34 @@ define( 'CACHE_MEMCACHED', 2 );  // MemCached, must specify servers in $wgMemCac
 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)
+
+/**#@-*/
+
 ?>
index aa3f6d3..6e01113 100644 (file)
@@ -1229,6 +1229,37 @@ function wfElementClean( $element, $attribs = array(), $contents = '') {
        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,
index ae28c72..2b49627 100644 (file)
@@ -37,8 +37,9 @@ class Image
                $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
@@ -111,7 +112,7 @@ class Image
 
                // 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
@@ -126,6 +127,7 @@ class 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;
@@ -142,6 +144,7 @@ class Image
                                $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;
@@ -173,6 +176,7 @@ class Image
                                                                  'height' => $this->height,
                                                                  'bits' => $this->bits,
                                                                  'type' => $this->type,
+                                                                 'mime' => $this->mime,
                                                                  'metadata' => $this->metadata,
                                                                  'size' => $this->size);
 
@@ -190,7 +194,9 @@ class Image
                $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) {                    
@@ -206,42 +212,67 @@ class Image
                        }
                }
 
+
                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 );
        }
 
@@ -254,8 +285,12 @@ class Image
                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;
@@ -263,7 +298,7 @@ class Image
                        $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 ) {
@@ -273,7 +308,7 @@ class Image
                        $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;
@@ -283,7 +318,7 @@ class Image
                                $this->loadFromRow( $row );
                                
                                // Check for rows from a previous schema, quietly upgrade them
-                               if ( $this->type == -1 ) {
+                               if ( is_null($this->type) ) {
                                        $this->upgradeRow();
                                }
                        }
@@ -312,9 +347,20 @@ class Image
                $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;
        }
 
@@ -355,12 +401,27 @@ class Image
                        // 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
                );
@@ -402,8 +463,14 @@ class Image
        }
        
        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();
                }
@@ -451,19 +518,157 @@ class Image
        }
 
        /**
-        * 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
@@ -537,6 +742,9 @@ class Image
                }
                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) {
@@ -567,9 +775,17 @@ class Image
         */
        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;
        }
@@ -611,18 +827,24 @@ class Image
                        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();
                }
@@ -672,7 +894,7 @@ class Image
                }
                
                # Sanity check $width
-               if( $width <= 0 ) {
+               if( $width <= 0 || $this->width <= 0) {
                        # BZZZT
                        return null;
                }
@@ -721,11 +943,13 @@ class Image
                                }
                        }
                }
+               
                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
         */
@@ -735,7 +959,9 @@ class Image
                
                $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;
@@ -743,8 +969,8 @@ class Image
                                        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 {
@@ -756,8 +982,9 @@ class Image
                        # 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.
@@ -814,6 +1041,7 @@ class Image
                        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
@@ -884,6 +1112,21 @@ class Image
                        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.
@@ -898,6 +1141,9 @@ class Image
        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" ), 
@@ -926,16 +1172,6 @@ class Image
        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
@@ -977,15 +1213,6 @@ class Image
                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
         */
@@ -996,16 +1223,14 @@ class Image
                $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;
                }
 
@@ -1019,6 +1244,15 @@ class Image
 
                $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.
@@ -1029,7 +1263,9 @@ class Image
                                '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(),
@@ -1059,14 +1295,13 @@ class Image
                                        '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 */
@@ -1074,11 +1309,13 @@ class Image
                                        '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
@@ -1148,7 +1385,7 @@ class Image
         * @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) {
@@ -1208,6 +1445,9 @@ class Image
                
                # 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 ),
@@ -1450,7 +1690,7 @@ function wfIsBadImage( $name ) {
 
        return array_key_exists( $name, $titleList );
 }
-       
+
 
 
 /**
index 66f6628..6e8276e 100644 (file)
@@ -136,10 +136,14 @@ class ImagePage extends Article {
                $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 ) {
@@ -150,7 +154,8 @@ class ImagePage extends Article {
                                        $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();
@@ -163,14 +168,57 @@ class ImagePage extends Article {
                                        $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) {
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
new file mode 100644 (file)
index 0000000..076dcc2
--- /dev/null
@@ -0,0 +1,657 @@
+<?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;
+       }
+}
+
+?>
index 74fc78b..dd3181a 100644 (file)
@@ -207,7 +207,7 @@ class UploadForm {
                        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 ) ||
@@ -221,8 +221,12 @@ class UploadForm {
                 * 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() );
+                       }
                }
                
                /**
@@ -309,6 +313,8 @@ class UploadForm {
        function saveUploadedFile( $saveName, $tempName, $useRename = false ) {
                global $wgUploadDirectory, $wgOut;
 
+               $fname= "SpecialUpload::saveUploadedFile";
+               
                $dest = wfImageDir( $saveName );
                $archive = wfImageArchiveDir( $saveName );
                $this->mSavedFile = "{$dest}/{$saveName}";
@@ -324,7 +330,9 @@ class UploadForm {
                                  "${archive}/{$this->mUploadOldVersion}" );
                                return false;
                        }
-               } else {
+                       else wfDebug("$fname: moved file ".$this->mSavedFile." to ${archive}/{$this->mUploadOldVersion}\n");
+               } 
+               else {
                        $this->mUploadOldVersion = '';
                }
                
@@ -336,6 +344,8 @@ class UploadForm {
                        if( ! $success ) {
                                $wgOut->fileCopyError( $tempName, $this->mSavedFile );
                                return false;
+                       } else {
+                               wfDebug("$fname: wrote tempfile $tempName to ".$this->mSavedFile."\n");
                        }
                } else {
                        wfSuppressWarnings();
@@ -346,7 +356,9 @@ class UploadForm {
                                $wgOut->fileCopyError( $tempName, $this->mSavedFile );
                                return false;
                        }
+                       else wfDebug("$fname: wrote tempfile $tempName to ".$this->mSavedFile."\n");
                }
+               
                chmod( $this->mSavedFile, 0644 );
                return true;
        }
@@ -640,86 +652,47 @@ class UploadForm {
        }
        
        /**
-        * 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" );
@@ -727,66 +700,219 @@ class UploadForm {
        }
        
        /**
-        * 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;
        }
        
+       
 }
 ?>
index ae993d8..e154d6a 100644 (file)
@@ -25,141 +25,39 @@ does not.</p>
                }
        }
        
+       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;
 }
 
 ?>
diff --git a/includes/mime.info b/includes/mime.info
new file mode 100644 (file)
index 0000000..b314be0
--- /dev/null
@@ -0,0 +1,76 @@
+#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]
diff --git a/includes/mime.types b/includes/mime.types
new file mode 100644 (file)
index 0000000..3a7fa39
--- /dev/null
@@ -0,0 +1,117 @@
+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
diff --git a/maintenance/archives/patch-img_media_type.sql b/maintenance/archives/patch-img_media_type.sql
new file mode 100644 (file)
index 0000000..c9ef854
--- /dev/null
@@ -0,0 +1,20 @@
+-- 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
index 9dc233f..a705dbd 100644 (file)
@@ -1,17 +1,20 @@
 -- 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
 );
 
 
index 9de5751..f17590f 100644 (file)
@@ -423,7 +423,9 @@ class ParserTest {
                                '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;
index 9926966..919b822 100644 (file)
@@ -531,9 +531,18 @@ CREATE TABLE /*$wgDBprefix*/image (
   -- 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.
@@ -574,7 +583,6 @@ CREATE TABLE /*$wgDBprefix*/oldimage (
   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 '',
index 4399f52..ce2935e 100644 (file)
@@ -46,6 +46,7 @@ $wgNewFields = array(
        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 ) {
index 73d2ca9..ff84d89 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -12,6 +12,9 @@ $wgNoOutputBuffer = true;
 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' );
 
@@ -25,6 +28,8 @@ if ( get_magic_quotes_gpc() ) {
        $width = $_REQUEST['w'];
 }
 
+$pre_render= isset($_REQUEST['r']) && $_REQUEST['r']!="0";
+
 // Some basic input validation
 
 $width = intval( $width );
@@ -34,7 +39,7 @@ $fileName = strtr( $fileName, '\\/', '__' );
 
 $imagePath = wfImageDir( $fileName ) . '/' . $fileName;
 $thumbName = "{$width}px-$fileName";
-if ( preg_match( '/\.svg$/', $fileName ) ) {
+if ( $pre_render ) {
        $thumbName .= '.png';
 }
 $thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;