22 use Psr\Log\LoggerAwareInterface;
23 use Psr\Log\LoggerInterface;
86 application/ogg ogx ogg ogm ogv oga spx opus
88 application/vnd.oasis.opendocument.chart odc
89 application/vnd.oasis.opendocument.chart-
template otc
90 application/vnd.oasis.opendocument.database odb
91 application/vnd.oasis.opendocument.formula odf
92 application/vnd.oasis.opendocument.formula-
template otf
93 application/vnd.oasis.opendocument.graphics odg
94 application/vnd.oasis.opendocument.graphics-
template otg
95 application/vnd.oasis.opendocument.image odi
96 application/vnd.oasis.opendocument.image-
template oti
97 application/vnd.oasis.opendocument.presentation odp
98 application/vnd.oasis.opendocument.presentation-
template otp
99 application/vnd.oasis.opendocument.spreadsheet ods
100 application/vnd.oasis.opendocument.spreadsheet-
template ots
101 application/vnd.oasis.opendocument.text odt
102 application/vnd.oasis.opendocument.text-master otm
103 application/vnd.oasis.opendocument.text-
template ott
104 application/vnd.oasis.opendocument.text-web oth
105 application/javascript js
106 application/x-shockwave-flash swf
107 audio/midi mid midi kar
108 audio/mpeg mpga mpa mp2 mp3
109 audio/x-aiff aif aiff aifc
111 audio/ogg oga spx ogg opus
112 audio/opus opus ogg oga ogg spx
115 image/jpeg jpeg jpg jpe
123 image/x-portable-pixmap ppm
127 video/ogg ogv ogm ogg
138 application/pdf [OFFICE]
139 application/vnd.oasis.opendocument.chart [OFFICE]
140 application/vnd.oasis.opendocument.chart-
template [OFFICE]
141 application/vnd.oasis.opendocument.database [OFFICE]
142 application/vnd.oasis.opendocument.formula [OFFICE]
143 application/vnd.oasis.opendocument.formula-
template [OFFICE]
144 application/vnd.oasis.opendocument.graphics [OFFICE]
145 application/vnd.oasis.opendocument.graphics-
template [OFFICE]
146 application/vnd.oasis.opendocument.image [OFFICE]
147 application/vnd.oasis.opendocument.image-
template [OFFICE]
148 application/vnd.oasis.opendocument.presentation [OFFICE]
149 application/vnd.oasis.opendocument.presentation-
template [OFFICE]
150 application/vnd.oasis.opendocument.spreadsheet [OFFICE]
151 application/vnd.oasis.opendocument.spreadsheet-
template [OFFICE]
152 application/vnd.oasis.opendocument.text [OFFICE]
153 application/vnd.oasis.opendocument.text-
template [OFFICE]
154 application/vnd.oasis.opendocument.text-master [OFFICE]
155 application/vnd.oasis.opendocument.text-web [OFFICE]
156 application/javascript
text/javascript application/x-javascript [EXECUTABLE]
157 application/x-shockwave-flash [MULTIMEDIA]
161 audio/mp3 audio/mpeg [AUDIO]
162 application/ogg audio/ogg video/ogg [MULTIMEDIA]
163 image/x-bmp image/x-ms-bmp image/bmp [BITMAP]
167 image/svg+xml [DRAWING]
169 image/vnd.djvu [BITMAP]
171 image/x-portable-pixmap [BITMAP]
176 unknown/unknown application/octet-stream application/x-empty [UNKNOWN]
195 $this->typeFile =
$params[
'typeFile'];
196 $this->infoFile =
$params[
'infoFile'];
197 $this->xmlTypes =
$params[
'xmlTypes'];
198 $this->initCallback = isset(
$params[
'initCallback'] )
201 $this->detectCallback = isset(
$params[
'detectCallback'] )
204 $this->guessCallback = isset(
$params[
'guessCallback'] )
207 $this->extCallback = isset(
$params[
'extCallback'] )
210 $this->logger = isset(
$params[
'logger'] )
212 : new \Psr\Log\NullLogger();
222 # Allow media handling extensions adding MIME-types and MIME-info
223 if ( $this->initCallback ) {
224 call_user_func( $this->initCallback, $this );
230 if ( $mimeTypeFile ) {
231 if ( is_file( $mimeTypeFile ) && is_readable( $mimeTypeFile ) ) {
232 $this->logger->info( __METHOD__ .
": loading mime types from $mimeTypeFile\n" );
234 $types .= file_get_contents( $mimeTypeFile );
236 $this->logger->info( __METHOD__ .
": can't load mime types from $mimeTypeFile\n" );
239 $this->logger->info( __METHOD__ .
240 ": no mime types file defined, using built-ins only.\n" );
245 $types = str_replace( [
"\r\n",
"\n\r",
"\n\n",
"\r\r",
"\r" ],
"\n", $types );
246 $types = str_replace(
"\t",
" ", $types );
248 $this->mimetoExt = [];
249 $this->mExtToMime = [];
251 $lines = explode(
"\n", $types );
257 if ( strpos(
$s,
'#' ) === 0 ) {
261 $s = strtolower(
$s );
262 $i = strpos(
$s,
' ' );
264 if ( $i ===
false ) {
269 $ext = trim( substr(
$s, $i + 1 ) );
271 if ( empty(
$ext ) ) {
275 if ( !empty( $this->mimetoExt[
$mime] ) ) {
281 $extensions = explode(
' ',
$ext );
283 foreach ( $extensions
as $e ) {
289 if ( !empty( $this->mExtToMime[
$e] ) ) {
290 $this->mExtToMime[
$e] .=
' ' .
$mime;
305 if ( $mimeInfoFile ) {
306 if ( is_file( $mimeInfoFile ) && is_readable( $mimeInfoFile ) ) {
307 $this->logger->info( __METHOD__ .
": loading mime info from $mimeInfoFile\n" );
309 $info .= file_get_contents( $mimeInfoFile );
311 $this->logger->info( __METHOD__ .
": can't load mime info from $mimeInfoFile\n" );
314 $this->logger->info( __METHOD__ .
315 ": no mime info file defined, using built-ins only.\n" );
320 $info = str_replace( [
"\r\n",
"\n\r",
"\n\n",
"\r\r",
"\r" ],
"\n", $info );
321 $info = str_replace(
"\t",
" ", $info );
323 $this->mimeTypeAliases = [];
324 $this->mediaTypes = [];
326 $lines = explode(
"\n", $info );
332 if ( strpos(
$s,
'#' ) === 0 ) {
336 $s = strtolower(
$s );
337 $i = strpos(
$s,
' ' );
339 if ( $i ===
false ) {
343 # print "processing MIME INFO line $s<br>";
346 if ( preg_match(
'!\[\s*(\w+)\s*\]!',
$s, $match ) ) {
347 $s = preg_replace(
'!\[\s*(\w+)\s*\]!',
'',
$s );
348 $mtype = trim( strtoupper( $match[1] ) );
353 $m = explode(
' ',
$s );
355 if ( !isset( $this->mediaTypes[$mtype] ) ) {
356 $this->mediaTypes[$mtype] = [];
361 if ( empty(
$mime ) ) {
365 $this->mediaTypes[$mtype][] =
$mime;
368 if (
count( $m ) > 1 ) {
370 $mCount =
count( $m );
371 for ( $i = 1; $i < $mCount; $i += 1 ) {
373 $this->mimeTypeAliases[
$mime] = $main;
390 $this->extraTypes .=
"\n" . $types;
400 $this->extraInfo .=
"\n" . $info;
415 if ( isset( $this->mimetoExt[
$mime] ) ) {
416 return $this->mimetoExt[
$mime];
420 if ( isset( $this->mimeTypeAliases[
$mime] ) ) {
422 if ( isset( $this->mimetoExt[
$mime] ) ) {
423 return $this->mimetoExt[
$mime];
440 $r = isset( $this->mExtToMime[
$ext] ) ? $this->mExtToMime[
$ext] :
null;
453 if ( is_null( $m ) ) {
459 $m = preg_replace(
'/\s.*$/',
'', $m );
482 $extension = strtolower( $extension );
483 return in_array( $extension,
$ext );
497 'image/gif',
'image/jpeg',
'image/png',
498 'image/x-bmp',
'image/xbm',
'image/tiff',
499 'image/jp2',
'image/jpeg2000',
'image/iff',
500 'image/xbm',
'image/x-xbitmap',
501 'image/vnd.wap.wbmp',
'image/vnd.xiff',
503 'application/x-shockwave-flash',
506 return in_array(
$mime, $types );
524 'gif',
'jpeg',
'jpg',
'png',
'swf',
'psd',
525 'bmp',
'tiff',
'tif',
'jpc',
'jp2',
526 'jpx',
'jb2',
'swc',
'iff',
'wbmp',
530 'djvu',
'ogx',
'ogg',
'ogv',
'oga',
'spx',
'opus',
531 'mid',
'pdf',
'wmf',
'xcf',
'webm',
'mkv',
'mka',
537 return in_array( strtolower( $extension ), $types );
552 if (
$mime ===
'unknown/unknown' ) {
554 $this->logger->info( __METHOD__ .
': refusing to guess mime type for .' .
555 "$ext file, we should have recognized it\n" );
561 } elseif (
$mime ===
'application/x-opc+zip' ) {
567 $this->logger->info( __METHOD__ .
568 ": refusing to guess better type for $mime file, " .
569 ".$ext is not a known OPC extension.\n" );
570 $mime =
'application/zip';
580 # Media handling extensions can improve the MIME detected
586 if ( isset( $this->mimeTypeAliases[
$mime] ) ) {
590 $this->logger->info( __METHOD__ .
": improved mime type for .$ext: $mime\n" );
610 $this->logger->info( __METHOD__ .
611 ": WARNING: use of the \$ext parameter is deprecated. " .
612 "Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
618 $this->logger->info( __METHOD__ .
619 ": internal type detection failed for $file (.$ext)...\n" );
623 if ( isset( $this->mimeTypeAliases[
$mime] ) ) {
627 $this->logger->info( __METHOD__ .
": guessed mime type of $file: $mime\n" );
643 MediaWiki\suppressWarnings();
644 $f = fopen( $file,
'rb' );
645 MediaWiki\restoreWarnings();
648 return 'unknown/unknown';
651 $fsize = filesize( $file );
652 if ( $fsize ===
false ) {
653 return 'unknown/unknown';
656 $head = fread( $f, 1024 );
657 $tailLength = min( 65558, $fsize );
658 if ( fseek( $f, -1 * $tailLength, SEEK_END ) === -1 ) {
659 throw new UnexpectedValueException(
660 "Seeking $tailLength bytes from EOF failed in " . __METHOD__ );
662 $tail = $tailLength ? fread( $f, $tailLength ) :
'';
665 $this->logger->info( __METHOD__ .
666 ": analyzing head and tail of $file for magic numbers.\n" );
671 'MThd' =>
'audio/midi',
672 'OggS' =>
'application/ogg',
676 "\x01\x00\x09\x00" =>
'application/x-msmetafile',
677 "\xd7\xcd\xc6\x9a" =>
'application/x-msmetafile',
678 '%PDF' =>
'application/pdf',
679 'gimp xcf' =>
'image/x-xcf',
682 'MZ' =>
'application/octet-stream',
683 "\xca\xfe\xba\xbe" =>
'application/octet-stream',
684 "\x7fELF" =>
'application/octet-stream',
687 foreach ( $headers
as $magic => $candidate ) {
688 if ( strncmp( $head, $magic, strlen( $magic ) ) == 0 ) {
689 $this->logger->info( __METHOD__ .
690 ": magic header in $file recognized as $candidate\n" );
696 if ( strncmp( $head, pack(
"C4", 0x1a, 0x45, 0xdf, 0xa3 ), 4 ) == 0 ) {
697 $doctype = strpos( $head,
"\x42\x82" );
700 $data = substr( $head, $doctype + 3, 8 );
701 if ( strncmp( $data,
"matroska", 8 ) == 0 ) {
702 $this->logger->info( __METHOD__ .
": recognized file as video/x-matroska\n" );
703 return "video/x-matroska";
704 } elseif ( strncmp( $data,
"webm", 4 ) == 0 ) {
705 $this->logger->info( __METHOD__ .
": recognized file as video/webm\n" );
709 $this->logger->info( __METHOD__ .
": unknown EBML file\n" );
710 return "unknown/unknown";
714 if ( strncmp( $head,
"RIFF", 4 ) == 0 &&
715 strncmp( substr( $head, 8, 7 ),
"WEBPVP8", 7 ) == 0
717 $this->logger->info( __METHOD__ .
": recognized file as image/webp\n" );
733 if ( ( strpos( $head,
'<?php' ) !==
false ) ||
734 ( strpos( $head,
"<\x00?\x00p\x00h\x00p" ) !==
false ) ||
735 ( strpos( $head,
"<\x00?\x00 " ) !==
false ) ||
736 ( strpos( $head,
"<\x00?\x00\n" ) !==
false ) ||
737 ( strpos( $head,
"<\x00?\x00\t" ) !==
false ) ||
738 ( strpos( $head,
"<\x00?\x00=" ) !==
false ) ) {
740 $this->logger->info( __METHOD__ .
": recognized $file as application/x-php\n" );
741 return 'application/x-php';
748 if ( $xml->wellFormed ) {
750 if ( isset(
$xmlTypes[$xml->getRootElement()] ) ) {
751 return $xmlTypes[$xml->getRootElement()];
753 return 'application/xml';
763 if ( substr( $head, 0, 2 ) ==
"#!" ) {
764 $script_type =
"ASCII";
765 } elseif ( substr( $head, 0, 5 ) ==
"\xef\xbb\xbf#!" ) {
766 $script_type =
"UTF-8";
767 } elseif ( substr( $head, 0, 7 ) ==
"\xfe\xff\x00#\x00!" ) {
768 $script_type =
"UTF-16BE";
769 } elseif ( substr( $head, 0, 7 ) ==
"\xff\xfe#\x00!" ) {
770 $script_type =
"UTF-16LE";
773 if ( $script_type ) {
774 if ( $script_type !==
"UTF-8" && $script_type !==
"ASCII" ) {
776 $pack = [
'UTF-16BE' =>
'n*',
'UTF-16LE' =>
'v*' ];
777 $chars = unpack( $pack[$script_type], substr( $head, 2 ) );
779 foreach ( $chars
as $codepoint ) {
780 if ( $codepoint < 128 ) {
781 $head .= chr( $codepoint );
790 if ( preg_match(
'%/?([^\s]+/)(\w+)%', $head, $match ) ) {
791 $mime =
"application/x-{$match[2]}";
792 $this->logger->info( __METHOD__ .
": shell script recognized as $mime\n" );
798 if ( strpos( $tail,
"PK\x05\x06" ) !==
false ) {
799 $this->logger->info( __METHOD__ .
": ZIP header present in $file\n" );
803 MediaWiki\suppressWarnings();
804 $gis = getimagesize( $file );
805 MediaWiki\restoreWarnings();
807 if ( $gis && isset( $gis[
'mime'] ) ) {
808 $mime = $gis[
'mime'];
809 $this->logger->info( __METHOD__ .
": getimagesize detected $file as $mime\n" );
813 # Media handling extensions can guess the MIME by content
814 # It's intentionally here so that if core is wrong about a type (false positive),
815 # people will hopefully nag and submit patches :)
817 # Some strings by reference for performance - assuming well-behaved hooks
820 $callback( $this, $head, $tail, $file,
$mime );
840 if (
$ext ) { # TODO:
remove $ext param
841 $this->logger->info( __METHOD__ .
842 ": WARNING: use of the \$ext parameter is deprecated. " .
843 "Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
846 $mime =
'application/zip';
856 'presentation-template',
858 'spreadsheet-template',
866 $types =
'(?:' . implode(
'|', $opendocTypes ) .
')';
867 $opendocRegex =
"/^mimetype(application\/vnd\.oasis\.opendocument\.$types)/";
869 $openxmlRegex =
"/^\[Content_Types\].xml/";
873 $this->logger->info( __METHOD__ .
": detected $mime from ZIP archive\n" );
874 } elseif ( preg_match( $openxmlRegex, substr(
$header, 30 ) ) ) {
875 $mime =
"application/x-opc+zip";
876 # TODO: remove the block below, as soon as improveTypeFromExtension is used everywhere
877 if (
$ext !==
true &&
$ext !==
false ) {
888 $mime =
"application/zip";
891 $this->logger->info( __METHOD__ .
892 ": detected an Open Packaging Conventions archive: $mime\n" );
893 } elseif ( substr(
$header, 0, 8 ) ==
"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" &&
894 ( $headerpos = strpos( $tail,
"PK\x03\x04" ) ) !==
false &&
895 preg_match( $openxmlRegex, substr( $tail, $headerpos + 30 ) ) ) {
896 if ( substr(
$header, 512, 4 ) ==
"\xEC\xA5\xC1\x00" ) {
897 $mime =
"application/msword";
899 switch ( substr(
$header, 512, 6 ) ) {
900 case "\xEC\xA5\xC1\x00\x0E\x00":
901 case "\xEC\xA5\xC1\x00\x1C\x00":
902 case "\xEC\xA5\xC1\x00\x43\x00":
903 $mime =
"application/vnd.ms-powerpoint";
905 case "\xFD\xFF\xFF\xFF\x10\x00":
906 case "\xFD\xFF\xFF\xFF\x1F\x00":
907 case "\xFD\xFF\xFF\xFF\x22\x00":
908 case "\xFD\xFF\xFF\xFF\x23\x00":
909 case "\xFD\xFF\xFF\xFF\x28\x00":
910 case "\xFD\xFF\xFF\xFF\x29\x00":
911 case "\xFD\xFF\xFF\xFF\x10\x02":
912 case "\xFD\xFF\xFF\xFF\x1F\x02":
913 case "\xFD\xFF\xFF\xFF\x22\x02":
914 case "\xFD\xFF\xFF\xFF\x23\x02":
915 case "\xFD\xFF\xFF\xFF\x28\x02":
916 case "\xFD\xFF\xFF\xFF\x29\x02":
917 $mime =
"application/vnd.msexcel";
921 $this->logger->info( __METHOD__ .
922 ": detected a MS Office document with OPC trailer\n" );
924 $this->logger->info( __METHOD__ .
": unable to identify type of ZIP archive\n" );
949 $this->logger->info( __METHOD__ .
950 ": WARNING: use of the \$ext parameter is deprecated. "
951 .
"Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
957 $m = $callback( $file );
958 } elseif ( function_exists(
"finfo_open" ) && function_exists(
"finfo_file" ) ) {
959 $mime_magic_resource = finfo_open( FILEINFO_MIME );
961 if ( $mime_magic_resource ) {
962 $m = finfo_file( $mime_magic_resource, $file );
963 finfo_close( $mime_magic_resource );
965 $this->logger->info( __METHOD__ .
966 ": finfo_open failed on " . FILEINFO_MIME .
"!\n" );
969 $this->logger->info( __METHOD__ .
": no magic mime detector found!\n" );
974 $m = preg_replace(
'![;, ].*$!',
'', $m ); # strip charset,
etc
976 $m = strtolower( $m );
978 if ( strpos( $m,
'unknown' ) !==
false ) {
981 $this->logger->info( __METHOD__ .
": magic mime type of $file: $m\n" );
987 if (
$ext ===
true ) {
988 $i = strrpos( $file,
'.' );
989 $ext = strtolower( $i ? substr( $file, $i + 1 ) :
'' );
993 $this->logger->info( __METHOD__ .
": refusing to guess mime type for .$ext file, "
994 .
"we should have recognized it\n" );
998 $this->logger->info( __METHOD__ .
": extension mime type of $file: $m\n" );
1005 $this->logger->info( __METHOD__ .
": failed to guess mime type for $file!\n" );
1006 return 'unknown/unknown';
1037 if (
$mime ==
'application/ogg' && file_exists(
$path ) ) {
1040 $f = fopen(
$path,
"rt" );
1044 $head = fread( $f, 256 );
1047 $head = str_replace(
'ffmpeg2theora',
'', strtolower( $head ) );
1050 if ( strpos( $head,
'theora' ) !==
false ) {
1052 } elseif ( strpos( $head,
'vorbis' ) !==
false ) {
1054 } elseif ( strpos( $head,
'flac' ) !==
false ) {
1056 } elseif ( strpos( $head,
'speex' ) !==
false ) {
1058 } elseif ( strpos( $head,
'opus' ) !==
false ) {
1076 $i = strrpos(
$path,
'.' );
1077 $e = strtolower( $i ? substr(
$path, $i + 1 ) :
'' );
1088 $i = strpos(
$mime,
'/' );
1089 if ( $i !==
false ) {
1090 $major = substr(
$mime, 0, $i );
1116 if ( strpos( $extMime,
'.' ) === 0 ) {
1123 $m = explode(
' ', $m );
1126 if ( isset( $this->mimeTypeAliases[$extMime] ) ) {
1127 $extMime = $this->mimeTypeAliases[$extMime];
1134 foreach ( $this->mediaTypes
as $type => $codes ) {
1135 if ( in_array(
$mime, $codes,
true ) ) {
1155 return $ca->getRealMimesFromData( $fileName, $chunk, $proposed );
1164 if ( is_null( $this->IEAnalyzer ) ) {