]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/MimeMagic.php
MediaWiki 1.15.0
[autoinstallsdev/mediawiki.git] / includes / MimeMagic.php
1 <?php
2 /** Module defining helper functions for detecting and dealing with mime types.
3  *
4  */
5
6  /** Defines a set of well known mime types
7  * This is used as a fallback to mime.types files.
8  * An extensive list of well known mime types is provided by
9  * the file mime.types in the includes directory.
10  */
11 define('MM_WELL_KNOWN_MIME_TYPES',<<<END_STRING
12 application/ogg ogg ogm ogv
13 application/pdf pdf
14 application/vnd.oasis.opendocument.chart odc
15 application/vnd.oasis.opendocument.chart-template otc
16 application/vnd.oasis.opendocument.formula odf
17 application/vnd.oasis.opendocument.formula-template otf
18 application/vnd.oasis.opendocument.graphics odg
19 application/vnd.oasis.opendocument.graphics-template otg
20 application/vnd.oasis.opendocument.image odi
21 application/vnd.oasis.opendocument.image-template oti
22 application/vnd.oasis.opendocument.presentation odp
23 application/vnd.oasis.opendocument.presentation-template otp
24 application/vnd.oasis.opendocument.spreadsheet ods
25 application/vnd.oasis.opendocument.spreadsheet-template ots
26 application/vnd.oasis.opendocument.text odt
27 application/vnd.oasis.opendocument.text-template ott
28 application/vnd.oasis.opendocument.text-master otm
29 application/vnd.oasis.opendocument.text-web oth
30 application/x-javascript js
31 application/x-shockwave-flash swf
32 audio/midi mid midi kar
33 audio/mpeg mpga mpa mp2 mp3
34 audio/x-aiff aif aiff aifc
35 audio/x-wav wav
36 audio/ogg ogg
37 image/x-bmp bmp
38 image/gif gif
39 image/jpeg jpeg jpg jpe
40 image/png png
41 image/svg+xml image/svg svg
42 image/tiff tiff tif
43 image/vnd.djvu image/x.djvu image/x-djvu djvu
44 image/x-portable-pixmap ppm
45 image/x-xcf xcf
46 text/plain txt
47 text/html html htm
48 video/ogg ogm ogg ogv
49 video/mpeg mpg mpeg
50 END_STRING
51 );
52
53  /** Defines a set of well known mime info entries
54  * This is used as a fallback to mime.info files.
55  * An extensive list of well known mime types is provided by
56  * the file mime.info in the includes directory.
57  */
58 define('MM_WELL_KNOWN_MIME_INFO', <<<END_STRING
59 application/pdf [OFFICE]
60 application/vnd.oasis.opendocument.chart [OFFICE]
61 application/vnd.oasis.opendocument.chart-template [OFFICE]
62 application/vnd.oasis.opendocument.formula [OFFICE]
63 application/vnd.oasis.opendocument.formula-template [OFFICE]
64 application/vnd.oasis.opendocument.graphics [OFFICE]
65 application/vnd.oasis.opendocument.graphics-template [OFFICE]
66 application/vnd.oasis.opendocument.image [OFFICE]
67 application/vnd.oasis.opendocument.image-template [OFFICE]
68 application/vnd.oasis.opendocument.presentation [OFFICE]
69 application/vnd.oasis.opendocument.presentation-template [OFFICE]
70 application/vnd.oasis.opendocument.spreadsheet [OFFICE]
71 application/vnd.oasis.opendocument.spreadsheet-template [OFFICE]
72 application/vnd.oasis.opendocument.text [OFFICE]
73 application/vnd.oasis.opendocument.text-template [OFFICE]
74 application/vnd.oasis.opendocument.text-master [OFFICE]
75 application/vnd.oasis.opendocument.text-web [OFFICE]
76 text/javascript application/x-javascript [EXECUTABLE]
77 application/x-shockwave-flash [MULTIMEDIA]
78 audio/midi [AUDIO]
79 audio/x-aiff [AUDIO]
80 audio/x-wav [AUDIO]
81 audio/mp3 audio/mpeg [AUDIO]
82 application/ogg audio/ogg video/ogg [MULTIMEDIA]
83 image/x-bmp image/bmp [BITMAP]
84 image/gif [BITMAP]
85 image/jpeg [BITMAP]
86 image/png [BITMAP]
87 image/svg+xml [DRAWING]
88 image/tiff [BITMAP]
89 image/vnd.djvu [BITMAP]
90 image/x-xcf [BITMAP]
91 image/x-portable-pixmap [BITMAP]
92 text/plain [TEXT]
93 text/html [TEXT]
94 video/ogg [VIDEO]
95 video/mpeg [VIDEO]
96 unknown/unknown application/octet-stream application/x-empty [UNKNOWN]
97 END_STRING
98 );
99
100 #note: because this file is possibly included by a function,
101 #we need to access the global scope explicitely!
102 global $wgLoadFileinfoExtension;
103
104 if ($wgLoadFileinfoExtension) {
105         if(!extension_loaded('fileinfo')) dl('fileinfo.' . PHP_SHLIB_SUFFIX);
106 }
107
108 /**
109  * Implements functions related to mime types such as detection and mapping to
110  * file extension.
111  *
112  * Instances of this class are stateles, there only needs to be one global instance
113  * of MimeMagic. Please use MimeMagic::singleton() to get that instance.
114  */
115 class MimeMagic {
116
117         /**
118         * Mapping of media types to arrays of mime types.
119         * This is used by findMediaType and getMediaType, respectively
120         */
121         var $mMediaTypes= NULL;
122
123         /** Map of mime type aliases
124         */
125         var $mMimeTypeAliases= NULL;
126
127         /** map of mime types to file extensions (as a space seprarated list)
128         */
129         var $mMimeToExt= NULL;
130
131         /** map of file extensions types to mime types (as a space seprarated list)
132         */
133         var $mExtToMime= NULL;
134
135         /** IEContentAnalyzer instance
136          */
137         var $mIEAnalyzer;
138
139         /** The singleton instance
140          */
141         private static $instance;
142
143         /** Initializes the MimeMagic object. This is called by MimeMagic::singleton().
144         *
145         * This constructor parses the mime.types and mime.info files and build internal mappings.
146         */
147         function __construct() {
148                 /*
149                 *   --- load mime.types ---
150                 */
151
152                 global $wgMimeTypeFile, $IP;
153
154                 $types = MM_WELL_KNOWN_MIME_TYPES;
155
156                 if ( $wgMimeTypeFile == 'includes/mime.types' ) {
157                         $wgMimeTypeFile = "$IP/$wgMimeTypeFile";
158                 }
159
160                 if ( $wgMimeTypeFile ) {
161                         if ( is_file( $wgMimeTypeFile ) and is_readable( $wgMimeTypeFile ) ) {
162                                 wfDebug( __METHOD__.": loading mime types from $wgMimeTypeFile\n" );
163                                 $types .= "\n";
164                                 $types .= file_get_contents( $wgMimeTypeFile );
165                         } else {
166                                 wfDebug( __METHOD__.": can't load mime types from $wgMimeTypeFile\n" );
167                         }
168                 } else {
169                         wfDebug( __METHOD__.": no mime types file defined, using build-ins only.\n" );
170                 }
171
172                 $types = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $types );
173                 $types = str_replace( "\t", " ", $types );
174
175                 $this->mMimeToExt = array();
176                 $this->mToMime = array();
177
178                 $lines = explode( "\n",$types );
179                 foreach ( $lines as $s ) {
180                         $s = trim( $s );
181                         if ( empty( $s ) ) continue;
182                         if ( strpos( $s, '#' ) === 0 ) continue;
183
184                         $s = strtolower( $s );
185                         $i = strpos( $s, ' ' );
186
187                         if ( $i === false ) continue;
188
189                         #print "processing MIME line $s<br>";
190
191                         $mime = substr( $s, 0, $i );
192                         $ext = trim( substr($s, $i+1 ) );
193
194                         if ( empty( $ext ) ) continue;
195
196                         if ( !empty( $this->mMimeToExt[$mime] ) ) {
197                                 $this->mMimeToExt[$mime] .= ' ' . $ext;
198                         } else {
199                                 $this->mMimeToExt[$mime] = $ext;
200                         }
201
202                         $extensions = explode( ' ', $ext );
203
204                         foreach ( $extensions as $e ) {
205                                 $e = trim( $e );
206                                 if ( empty( $e ) ) continue;
207
208                                 if ( !empty( $this->mExtToMime[$e] ) ) {
209                                         $this->mExtToMime[$e] .= ' ' . $mime;
210                                 } else {
211                                         $this->mExtToMime[$e] = $mime;
212                                 }
213                         }
214                 }
215
216                 /*
217                 *   --- load mime.info ---
218                 */
219
220                 global $wgMimeInfoFile;
221                 if ( $wgMimeInfoFile == 'includes/mime.info' ) {
222                         $wgMimeInfoFile = "$IP/$wgMimeInfoFile";
223                 }
224
225                 $info = MM_WELL_KNOWN_MIME_INFO;
226
227                 if ( $wgMimeInfoFile ) {
228                         if ( is_file( $wgMimeInfoFile ) and is_readable( $wgMimeInfoFile ) ) {
229                                 wfDebug( __METHOD__.": loading mime info from $wgMimeInfoFile\n" );
230                                 $info .= "\n";
231                                 $info .= file_get_contents( $wgMimeInfoFile );
232                         } else {
233                                 wfDebug(__METHOD__.": can't load mime info from $wgMimeInfoFile\n");
234                         }
235                 } else {
236                         wfDebug(__METHOD__.": no mime info file defined, using build-ins only.\n");
237                 }
238
239                 $info = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $info);
240                 $info = str_replace( "\t", " ", $info );
241
242                 $this->mMimeTypeAliases = array();
243                 $this->mMediaTypes = array();
244
245                 $lines = explode( "\n", $info );
246                 foreach ( $lines as $s ) {
247                         $s = trim( $s );
248                         if ( empty( $s ) ) continue;
249                         if ( strpos( $s, '#' ) === 0 ) continue;
250
251                         $s = strtolower( $s );
252                         $i = strpos( $s, ' ' );
253
254                         if ( $i === false ) continue;
255
256                         #print "processing MIME INFO line $s<br>";
257
258                         $match = array();
259                         if ( preg_match( '!\[\s*(\w+)\s*\]!', $s, $match ) ) {
260                                 $s = preg_replace( '!\[\s*(\w+)\s*\]!', '', $s );
261                                 $mtype = trim( strtoupper( $match[1] ) );
262                         } else {
263                                 $mtype = MEDIATYPE_UNKNOWN;
264                         }
265
266                         $m = explode( ' ', $s );
267
268                         if ( !isset( $this->mMediaTypes[$mtype] ) ) {
269                                 $this->mMediaTypes[$mtype] = array();
270                         }
271
272                         foreach ( $m as $mime ) {
273                                 $mime = trim( $mime );
274                                 if ( empty( $mime ) ) continue;
275
276                                 $this->mMediaTypes[$mtype][] = $mime;
277                         }
278
279                         if ( sizeof( $m ) > 1 ) {
280                                 $main = $m[0];
281                                 for ( $i=1; $i<sizeof($m); $i += 1 ) {
282                                         $mime = $m[$i];
283                                         $this->mMimeTypeAliases[$mime] = $main;
284                                 }
285                         }
286                 }
287
288         }
289
290         /**
291          * Get an instance of this class
292          */
293         static function &singleton() {
294                 if ( !isset( self::$instance ) ) {
295                         self::$instance = new MimeMagic;
296                 }
297                 return self::$instance;
298         }
299
300         /** returns a list of file extensions for a given mime type
301         * as a space separated string.
302         */
303         function getExtensionsForType( $mime ) {
304                 $mime = strtolower( $mime );
305
306                 $r = @$this->mMimeToExt[$mime];
307
308                 if ( @!$r and isset( $this->mMimeTypeAliases[$mime] ) ) {
309                         $mime = $this->mMimeTypeAliases[$mime];
310                         $r = @$this->mMimeToExt[$mime];
311                 }
312
313                 return $r;
314         }
315
316         /** returns a list of mime types for a given file extension
317         * as a space separated string.
318         */
319         function getTypesForExtension( $ext ) {
320                 $ext = strtolower( $ext );
321
322                 $r = isset( $this->mExtToMime[$ext] ) ? $this->mExtToMime[$ext] : null;
323                 return $r;
324         }
325
326         /** returns a single mime type for a given file extension.
327         * This is always the first type from the list returned by getTypesForExtension($ext).
328         */
329         function guessTypesForExtension( $ext ) {
330                 $m = $this->getTypesForExtension( $ext );
331                 if ( is_null( $m ) ) return NULL;
332
333                 $m = trim( $m );
334                 $m = preg_replace( '/\s.*$/', '', $m );
335
336                 return $m;
337         }
338
339
340         /** tests if the extension matches the given mime type.
341         * returns true if a match was found, NULL if the mime type is unknown,
342         * and false if the mime type is known but no matches where found.
343         */
344         function isMatchingExtension( $extension, $mime ) {
345                 $ext = $this->getExtensionsForType( $mime );
346
347                 if ( !$ext ) {
348                         return NULL;  //unknown
349                 }
350
351                 $ext = explode( ' ', $ext );
352
353                 $extension = strtolower( $extension );
354                 if ( in_array( $extension, $ext ) ) {
355                         return true;
356                 }
357
358                 return false;
359         }
360
361         /** returns true if the mime type is known to represent
362         * an image format supported by the PHP GD library.
363         */
364         function isPHPImageType( $mime ) {
365                 #as defined by imagegetsize and image_type_to_mime
366                 static $types = array(
367                         'image/gif', 'image/jpeg', 'image/png',
368                         'image/x-bmp', 'image/xbm', 'image/tiff',
369                         'image/jp2', 'image/jpeg2000', 'image/iff',
370                         'image/xbm', 'image/x-xbitmap',
371                         'image/vnd.wap.wbmp', 'image/vnd.xiff',
372                         'image/x-photoshop',
373                         'application/x-shockwave-flash',
374                 );
375
376                 return in_array( $mime, $types );
377         }
378
379         /**
380          * Returns true if the extension represents a type which can
381          * be reliably detected from its content. Use this to determine
382          * whether strict content checks should be applied to reject
383          * invalid uploads; if we can't identify the type we won't
384          * be able to say if it's invalid.
385          *
386          * @todo Be more accurate when using fancy mime detector plugins;
387          *       right now this is the bare minimum getimagesize() list.
388          * @return bool
389          */
390         function isRecognizableExtension( $extension ) {
391                 static $types = array(
392                         // Types recognized by getimagesize()
393                         'gif', 'jpeg', 'jpg', 'png', 'swf', 'psd',
394                         'bmp', 'tiff', 'tif', 'jpc', 'jp2',
395                         'jpx', 'jb2', 'swc', 'iff', 'wbmp',
396                         'xbm',
397
398                         // Formats we recognize magic numbers for
399                         'djvu', 'ogg', 'ogv', 'mid', 'pdf', 'wmf', 'xcf',
400
401                         // XML formats we sure hope we recognize reliably
402                         'svg',
403                 );
404                 return in_array( strtolower( $extension ), $types );
405         }
406
407
408         /** mime type detection. This uses detectMimeType to detect the mime type of the file,
409         * but applies additional checks to determine some well known file formats that may be missed
410         * or misinterpreter by the default mime detection (namely xml based formats like XHTML or SVG).
411         *
412         * @param string $file The file to check
413         * @param mixed $ext The file extension, or true to extract it from the filename.
414         *                   Set it to false to ignore the extension.
415         *
416         * @return string the mime type of $file
417         */
418         function guessMimeType( $file, $ext = true ) {
419                 $mime = $this->doGuessMimeType( $file, $ext );
420
421                 if( !$mime ) {
422                         wfDebug( __METHOD__.": internal type detection failed for $file (.$ext)...\n" );
423                         $mime = $this->detectMimeType( $file, $ext );
424                 }
425
426                 if ( isset( $this->mMimeTypeAliases[$mime] ) ) {
427                         $mime = $this->mMimeTypeAliases[$mime];
428                 }
429
430                 wfDebug(__METHOD__.": final mime type of $file: $mime\n");
431                 return $mime;
432         }
433
434         function doGuessMimeType( $file, $ext = true ) {
435                 // Read a chunk of the file
436                 wfSuppressWarnings();
437                 $f = fopen( $file, "rt" );
438                 wfRestoreWarnings();
439                 if( !$f ) return "unknown/unknown";
440                 $head = fread( $f, 1024 );
441                 fseek( $f, -65558, SEEK_END );
442                 $tail = fread( $f, 65558 ); // 65558 = maximum size of a zip EOCDR
443                 fclose( $f );
444
445                 // Hardcode a few magic number checks...
446                 $headers = array(
447                         // Multimedia...
448                         'MThd'             => 'audio/midi',
449                         'OggS'             => 'application/ogg',
450
451                         // Image formats...
452                         // Note that WMF may have a bare header, no magic number.
453                         "\x01\x00\x09\x00" => 'application/x-msmetafile', // Possibly prone to false positives?
454                         "\xd7\xcd\xc6\x9a" => 'application/x-msmetafile',
455                         '%PDF'             => 'application/pdf',
456                         'gimp xcf'         => 'image/x-xcf',
457
458                         // Some forbidden fruit...
459                         'MZ'               => 'application/octet-stream', // DOS/Windows executable
460                         "\xca\xfe\xba\xbe" => 'application/octet-stream', // Mach-O binary
461                         "\x7fELF"          => 'application/octet-stream', // ELF binary
462                 );
463
464                 foreach( $headers as $magic => $candidate ) {
465                         if( strncmp( $head, $magic, strlen( $magic ) ) == 0 ) {
466                                 wfDebug( __METHOD__ . ": magic header in $file recognized as $candidate\n" );
467                                 return $candidate;
468                         }
469                 }
470
471                 /*
472                  * look for PHP
473                  * Check for this before HTML/XML...
474                  * Warning: this is a heuristic, and won't match a file with a lot of non-PHP before.
475                  * It will also match text files which could be PHP. :)
476                  */
477                 if( ( strpos( $head, '<?php' ) !== false ) ||
478                     ( strpos( $head, '<? ' ) !== false ) ||
479                     ( strpos( $head, "<?\n" ) !== false ) ||
480                     ( strpos( $head, "<?\t" ) !== false ) ||
481                     ( strpos( $head, "<?=" ) !== false ) ||
482
483                     ( strpos( $head, "<\x00?\x00p\x00h\x00p" ) !== false ) ||
484                     ( strpos( $head, "<\x00?\x00 " ) !== false ) ||
485                     ( strpos( $head, "<\x00?\x00\n" ) !== false ) ||
486                     ( strpos( $head, "<\x00?\x00\t" ) !== false ) ||
487                     ( strpos( $head, "<\x00?\x00=" ) !== false ) ) {
488
489                         wfDebug( __METHOD__ . ": recognized $file as application/x-php\n" );
490                         return "application/x-php";
491                 }
492
493                 /*
494                  * look for XML formats (XHTML and SVG)
495                  */
496                 $xml = new XmlTypeCheck( $file );
497                 if( $xml->wellFormed ) {
498                         global $wgXMLMimeTypes;
499                         if( isset( $wgXMLMimeTypes[$xml->getRootElement()] ) ) {
500                                 return $wgXMLMimeTypes[$xml->getRootElement()];
501                         } else {
502                                 return 'application/xml';
503                         }
504                 }
505
506                 /*
507                  * look for shell scripts
508                  */
509                 $script_type = NULL;
510
511                 # detect by shebang
512                 if ( substr( $head, 0, 2) == "#!" ) {
513                         $script_type = "ASCII";
514                 } elseif ( substr( $head, 0, 5) == "\xef\xbb\xbf#!" ) {
515                         $script_type = "UTF-8";
516                 } elseif ( substr( $head, 0, 7) == "\xfe\xff\x00#\x00!" ) {
517                         $script_type = "UTF-16BE";
518                 } elseif ( substr( $head, 0, 7 ) == "\xff\xfe#\x00!" ) {
519                         $script_type= "UTF-16LE";
520                 }
521
522                 if ( $script_type ) {
523                         if ( $script_type !== "UTF-8" && $script_type !== "ASCII") {
524                                 // Quick and dirty fold down to ASCII!
525                                 $pack = array( 'UTF-16BE' => 'n*', 'UTF-16LE' => 'v*' );
526                                 $chars = unpack( $pack[$script_type], substr( $head, 2 ) );
527                                 $head = '';
528                                 foreach( $chars as $codepoint ) {
529                                         if( $codepoint < 128 ) {
530                                                 $head .= chr( $codepoint );
531                                         } else {
532                                                 $head .= '?';
533                                         }
534                                 }
535                         }
536
537                         $match = array();
538
539                         if ( preg_match( '%/?([^\s]+/)(\w+)%', $head, $match ) ) {
540                                 $mime = "application/x-{$match[2]}";
541                                 wfDebug( __METHOD__.": shell script recognized as $mime\n" );
542                                 return $mime;
543                         }
544                 }
545
546                 // Check for ZIP (before getimagesize)
547                 if ( strpos( $tail, "PK\x05\x06" ) !== false ) {
548                         wfDebug( __METHOD__.": ZIP header present at end of $file\n" );
549                         return $this->detectZipType( $head );
550                 }
551
552                 wfSuppressWarnings();
553                 $gis = getimagesize( $file );
554                 wfRestoreWarnings();
555
556                 if( $gis && isset( $gis['mime'] ) ) {
557                         $mime = $gis['mime'];
558                         wfDebug( __METHOD__.": getimagesize detected $file as $mime\n" );
559                         return $mime;
560                 }
561
562                 // Also test DjVu
563                 $deja = new DjVuImage( $file );
564                 if( $deja->isValid() ) {
565                         wfDebug( __METHOD__.": detected $file as image/vnd.djvu\n" );
566                         return 'image/vnd.djvu';
567                 }
568
569                 return false;
570         }
571         
572         /**
573          * Detect application-specific file type of a given ZIP file from its
574          * header data.  Currently works for OpenDocument types...
575          * If can't tell, returns 'application/zip'.
576          *
577          * @param string $header Some reasonably-sized chunk of file header
578          * @return string
579          */
580         function detectZipType( $header ) {
581                 $opendocTypes = array(
582                         'chart-template',
583                         'chart',
584                         'formula-template',
585                         'formula',
586                         'graphics-template',
587                         'graphics',
588                         'image-template',
589                         'image',
590                         'presentation-template',
591                         'presentation',
592                         'spreadsheet-template',
593                         'spreadsheet',
594                         'text-template',
595                         'text-master',
596                         'text-web',
597                         'text' );
598
599                 // http://lists.oasis-open.org/archives/office/200505/msg00006.html
600                 $types = '(?:' . implode( '|', $opendocTypes ) . ')';
601                 $opendocRegex = "/^mimetype(application\/vnd\.oasis\.opendocument\.$types)/";
602                 wfDebug( __METHOD__.": $opendocRegex\n" );
603                 
604                 if( preg_match( $opendocRegex, substr( $header, 30 ), $matches ) ) {
605                         $mime = $matches[1];
606                         wfDebug( __METHOD__.": detected $mime from ZIP archive\n" );
607                         return $mime;
608                 } else {
609                         wfDebug( __METHOD__.": unable to identify type of ZIP archive\n" );
610                         return 'application/zip';
611                 }
612         }
613
614         /** Internal mime type detection, please use guessMimeType() for application code instead.
615         * Detection is done using an external program, if $wgMimeDetectorCommand is set.
616         * Otherwise, the fileinfo extension and mime_content_type are tried (in this order), if they are available.
617         * If the dections fails and $ext is not false, the mime type is guessed from the file extension, using
618         * guessTypesForExtension.
619         * If the mime type is still unknown, getimagesize is used to detect the mime type if the file is an image.
620         * If no mime type can be determined, this function returns "unknown/unknown".
621         *
622         * @param string $file The file to check
623         * @param mixed $ext The file extension, or true to extract it from the filename.
624         *                   Set it to false to ignore the extension.
625         *
626         * @return string the mime type of $file
627         * @access private
628         */
629         function detectMimeType( $file, $ext = true ) {
630                 global $wgMimeDetectorCommand;
631
632                 $m = NULL;
633                 if ( $wgMimeDetectorCommand ) {
634                         $fn = wfEscapeShellArg( $file );
635                         $m = `$wgMimeDetectorCommand $fn`;
636                 } elseif ( function_exists( "finfo_open" ) && function_exists( "finfo_file" ) ) {
637
638                         # This required the fileinfo extension by PECL,
639                         # see http://pecl.php.net/package/fileinfo
640                         # This must be compiled into PHP
641                         #
642                         # finfo is the official replacement for the deprecated
643                         # mime_content_type function, see below.
644                         #
645                         # If you may need to load the fileinfo extension at runtime, set
646                         # $wgLoadFileinfoExtension in LocalSettings.php
647
648                         $mime_magic_resource = finfo_open(FILEINFO_MIME); /* return mime type ala mimetype extension */
649
650                         if ($mime_magic_resource) {
651                                 $m = finfo_file( $mime_magic_resource, $file );
652                                 finfo_close( $mime_magic_resource );
653                         } else {
654                                 wfDebug( __METHOD__.": finfo_open failed on ".FILEINFO_MIME."!\n" );
655                         }
656                 } elseif ( function_exists( "mime_content_type" ) ) {
657
658                         # NOTE: this function is available since PHP 4.3.0, but only if
659                         # PHP was compiled with --with-mime-magic or, before 4.3.2, with --enable-mime-magic.
660                         #
661                         # On Windows, you must set mime_magic.magicfile in php.ini to point to the mime.magic file bundeled with PHP;
662                         # sometimes, this may even be needed under linus/unix.
663                         #
664                         # Also note that this has been DEPRECATED in favor of the fileinfo extension by PECL, see above.
665                         # see http://www.php.net/manual/en/ref.mime-magic.php for details.
666
667                         $m = mime_content_type($file);
668                 } else {
669                         wfDebug( __METHOD__.": no magic mime detector found!\n" );
670                 }
671
672                 if ( $m ) {
673                         # normalize
674                         $m = preg_replace( '![;, ].*$!', '', $m ); #strip charset, etc
675                         $m = trim( $m );
676                         $m = strtolower( $m );
677
678                         if ( strpos( $m, 'unknown' ) !== false ) {
679                                 $m = NULL;
680                         } else {
681                                 wfDebug( __METHOD__.": magic mime type of $file: $m\n" );
682                                 return $m;
683                         }
684                 }
685
686                 # if desired, look at extension as a fallback.
687                 if ( $ext === true ) {
688                         $i = strrpos( $file, '.' );
689                         $ext = strtolower( $i ? substr( $file, $i + 1 ) : '' );
690                 }
691                 if ( $ext ) {
692                         if( $this->isRecognizableExtension( $ext ) ) {
693                                 wfDebug( __METHOD__. ": refusing to guess mime type for .$ext file, we should have recognized it\n" );
694                         } else {
695                                 $m = $this->guessTypesForExtension( $ext );
696                                 if ( $m ) {
697                                         wfDebug( __METHOD__.": extension mime type of $file: $m\n" );
698                                         return $m;
699                                 }
700                         }
701                 }
702
703                 #unknown type
704                 wfDebug( __METHOD__.": failed to guess mime type for $file!\n" );
705                 return "unknown/unknown";
706         }
707
708         /**
709         * Determine the media type code for a file, using its mime type, name and possibly
710         * its contents.
711         *
712         * This function relies on the findMediaType(), mapping extensions and mime
713         * types to media types.
714         *
715         * @todo analyse file if need be
716         * @todo look at multiple extension, separately and together.
717         *
718         * @param string $path full path to the image file, in case we have to look at the contents
719         *        (if null, only the mime type is used to determine the media type code).
720         * @param string $mime mime type. If null it will be guessed using guessMimeType.
721         *
722         * @return (int?string?) a value to be used with the MEDIATYPE_xxx constants.
723         */
724         function getMediaType( $path = NULL, $mime = NULL ) {
725                 if( !$mime && !$path ) return MEDIATYPE_UNKNOWN;
726
727                 # If mime type is unknown, guess it
728                 if( !$mime ) $mime = $this->guessMimeType( $path, false );
729
730                 # Special code for ogg - detect if it's video (theora),
731                 # else label it as sound.
732                 if( $mime == "application/ogg" && file_exists( $path ) ) {
733
734                         // Read a chunk of the file
735                         $f = fopen( $path, "rt" );
736                         if ( !$f ) return MEDIATYPE_UNKNOWN;
737                         $head = fread( $f, 256 );
738                         fclose( $f );
739
740                         $head = strtolower( $head );
741
742                         # This is an UGLY HACK, file should be parsed correctly
743                         if ( strpos( $head, 'theora' ) !== false ) return MEDIATYPE_VIDEO;
744                         elseif ( strpos( $head, 'vorbis' ) !== false ) return MEDIATYPE_AUDIO;
745                         elseif ( strpos( $head, 'flac' ) !== false ) return MEDIATYPE_AUDIO;
746                         elseif ( strpos( $head, 'speex' ) !== false ) return MEDIATYPE_AUDIO;
747                         else return MEDIATYPE_MULTIMEDIA;
748                 }
749
750                 # check for entry for full mime type
751                 if( $mime ) {
752                         $type = $this->findMediaType( $mime );
753                         if( $type !== MEDIATYPE_UNKNOWN ) return $type;
754                 }
755
756                 # Check for entry for file extension
757                 $e = NULL;
758                 if ( $path ) {
759                         $i = strrpos( $path, '.' );
760                         $e = strtolower( $i ? substr( $path, $i + 1 ) : '' );
761
762                         # TODO: look at multi-extension if this fails, parse from full path
763
764                         $type = $this->findMediaType( '.' . $e );
765                         if ( $type !== MEDIATYPE_UNKNOWN ) return $type;
766                 }
767
768                 # Check major mime type
769                 if( $mime ) {
770                         $i = strpos( $mime, '/' );
771                         if( $i !== false ) {
772                                 $major = substr( $mime, 0, $i );
773                                 $type = $this->findMediaType( $major );
774                                 if( $type !== MEDIATYPE_UNKNOWN ) return $type;
775                         }
776                 }
777
778                 if( !$type ) $type = MEDIATYPE_UNKNOWN;
779
780                 return $type;
781         }
782
783         /** returns a media code matching the given mime type or file extension.
784         * File extensions are represented by a string starting with a dot (.) to
785         * distinguish them from mime types.
786         *
787         * This funktion relies on the mapping defined by $this->mMediaTypes
788         * @access private
789         */
790         function findMediaType( $extMime ) {
791                 if ( strpos( $extMime, '.' ) === 0 ) { #if it's an extension, look up the mime types
792                         $m = $this->getTypesForExtension( substr( $extMime, 1 ) );
793                         if ( !$m ) return MEDIATYPE_UNKNOWN;
794
795                         $m = explode( ' ', $m );
796                 } else {
797                         # Normalize mime type
798                         if ( isset( $this->mMimeTypeAliases[$extMime] ) ) {
799                                 $extMime = $this->mMimeTypeAliases[$extMime];
800                         }
801
802                         $m = array($extMime);
803                 }
804
805                 foreach ( $m as $mime ) {
806                         foreach ( $this->mMediaTypes as $type => $codes ) {
807                                 if ( in_array($mime, $codes, true ) ) {
808                                         return $type;
809                                 }
810                         }
811                 }
812
813                 return MEDIATYPE_UNKNOWN;
814         }
815
816         /**
817          * Get the MIME types that various versions of Internet Explorer would 
818          * detect from a chunk of the content.
819          *
820          * @param string $fileName The file name (unused at present)
821          * @param string $chunk The first 256 bytes of the file
822          * @param string $proposed The MIME type proposed by the server
823          */
824         public function getIEMimeTypes( $fileName, $chunk, $proposed ) {
825                 $ca = $this->getIEContentAnalyzer();
826                 return $ca->getRealMimesFromData( $fileName, $chunk, $proposed );
827         }
828
829         /**
830          * Get a cached instance of IEContentAnalyzer
831          */
832         protected function getIEContentAnalyzer() {
833                 if ( is_null( $this->mIEAnalyzer ) ) {
834                         $this->mIEAnalyzer = new IEContentAnalyzer;
835                 }
836                 return $this->mIEAnalyzer;
837         }
838 }