I’m working on a project where I needed to generate a MIME type given a file name. Not only did I need to create a solution that worked, I also needed the solution to be compatible with PHP 4/5 and not require any additional software to be installed on the host. I thought this would be a simple matter of finding a PHP function that does this. Unfortunately, things were not as simple as this.
Problems with finfo_open
I found a very helpful PHP, Mime Types and Fileinfo post on Jelly and Custard. The “Mime Types in PHP 4.x” seemed to be exactly what I wanted. I quickly tried it out on my server, and it failed instantly with the following error:
PHP Fatal error: Call to undefined function finfo_open() in ....
Since I have PHP 5.2.6 running on my dev server, I was very confused since the post said that this was a “PHP 4.x” solution. I pulled up the PHP doc on the finfo_open
function and was very surprised to see that the function is a PHP 5.3.0+ function. Fortunately, my version of PHP was just before this so that I could actually catch the error.
Turns out that the Jelly and Custard post points to another post, Installing PECL Modules, where instructions are given for installing the Fileinfo PECL Package. Installing this package allows versions prior to 5.3.0 to use the finfo_*
functions via PECL.
This is not what I want. I’m working toward a solution that doesn’t require the installation of any additional software, and so far I’ve only seen solutions that require this. There has to be a better way.
Problems with mime_content_type
I then found the mime_content_type
function. This looks better. However, PHP has marked this function as deprecated due to the PECL Fileinfo package. Relying on a deprecated function has numerous problems: it may throw warnings if used on versions of PHP that know the function is deprecated, most likely won’t receive any updates in the future, and could possibly be removed from future versions of PHP.
In addition to the deprecated issue, the mime_content_type
function is laced with problems. In order to use the function the PHP on your system must have been built with the --with-mime-magic
option. The function also relies upon the mime_magic.magicfile
ini configuration to tell it where to find the magic file used to detect the MIME type of the file. This magic file may or may not exist/may or may not be readable.
Initial Testing
These problems led to interesting results when I tested it on my CentOS dedicated and Hostgator servers.
On my CentOS 5.2 dedicated server, I have PHP 5.2.6. The PHP build on this server was not built with the --with-mime-magic
option, so it doesn’t have access to the mime_content_type
function at all. In addition to this, since the version is just before 5.3 and I haven’t installed the Fileinfo PECL package, my dedicated server does not have access to either of the official PHP solutions without installing additional software.
On my Hostgator shared server, I have access to PHP 5.2.8. Unlike my dedicated server, this server’s PHP build was built with the --with-mime-magic
option. “This is great,” I thought. I ran a test, and the mime_content_type
function did indeed exist. Like my CentOS server, my Hostgator server does not have the Fileinfo PECL package and does not have PHP 5.3, so the finfo_*
functions are no go.
I did some more testing on my Hostgator server and was disappointed to find that the mime_content_type
function exists yet is completely worthless. I tested file after file ranging from simple text files, to HTML documents, to a variety of image types. Every single test failed to produce a MIME type. When I say “failed”, I don’t mean that the program crashed with an error. The failure was worse than this, it simply returned an empty string to every single request.
I found that my Hostgator server’s PHP is set up to use the /usr/local/apache/conf/magic
file to do it’s MIME magic. However, this file is not able to be read by my user. This means that the version of PHP might as well not have been built with the --with-mime-magic
option at all.
It seems clear to me by now that there will not be an easy solution to this problem.
Final Solution
What I need is a solution that will first try to use the Fileinfo functions since they are the current standard. It will then fall back to using the mime_content_type
function if and only if the function exists. Since the function is deprecated and PHP versions don’t actually package the replacement functions natively until 5.3.0, I need to also protect the code against conditions where neither Fileinfo nor mime_content_type
are available. The final fallback will be manually generating the MIME type based upon an array match.
Using the fallback is not desirable since I probably won’t be updating the MIME types in the array very often, if at all. However, having it is better than having the code completely fail for common, present day MIME types. Hopefully, the conditions necessary to rely upon this fallback will become more and more rare as time goes on.
In addition to checking for the existence of the functions I’ll try to use, I need to make sure that the methods tried actually produce results. If a method fails to produce a non-empty value, I’ll move on to the next method.
The Code
I got the original idea for this code from a comment by svogal on the PHP doc site. I modified it to match my desired final solution.
The code is far to large to post here. You can download it here.
The array that I built for the mime types is quite large. Since I have a /etc/mime.types
file (which is standard in most distros and provided by the mailcap package), I simply used it to generate my array. I quickly built a Perl script that parses through the mime.types
file and outputs a file containing the PHP code to create the array. This script makes it easy for me to update the array any time the mime.types
file is updated.
You can download my Perl script here. Simply run perl generateMimeTypes
from the command line to build the array. The array code is put in a file called mime_type_var.code
.
Examples
Simple Use
To use the code, simply include or require the code in your own script and then call the get_file_mime_type
function by passing the file’s path as the parameter.
For example:
require( 'mime_type_lib.php' ); $mime_type = get_file_mime_type( '/home/user/public_html/image.jpg' ); echo "$mime_type\n";
This produces the following output:
[gaarai@work ~]$ php example.php image/jpeg
Note that PHP’s more advanced functions have the ability to dig into the content of the file to identify the MIME type, so if your system can make use of those functions and the file is not a JPEG image, the results could vary.
Using the debug Parameter
I’ve also provided a debug parameter that allows you to also get information on what method was used to get the MIME type. This may be helpful if you need to determine how sure you are that the detected MIME type is accurate. When the debug parameter is used, an associative array will be returned with a mime_type
key and a method
key.
require( 'mime_type_lib.php' ); $data = get_file_mime_type( '/home/user/public_html/image.jpg', true ); echo "MIME Type: ${data['mime_type']}\n"; echo "Method: ${data['method']}\n";
This produces the following output:
[gaarai@work ~]$ php example2.php MIME Type: image/jpeg Method: from_array
The possible methods are: fileinfo
, mime_content_type
, from_array
, and last_resort
. The first two should be self-explanatory. from_array
means that the resulting MIME type was pulled from the array built into the function. last_resort
means that the type could not be identified which results in a generic MIME type of application/octet-stream
being used.
Conclusion
Frankly, I’m disappointed in the solutions provided by PHP to detect MIME types. I have access to a very stable, highly-regarded shared host and an extremely powerful, up-to-date dedicated host, neither of which have the ability to use either of the PHP solutions to detect MIME types without adding additional software.
I don’t remember which version of PHP my dedicated box started with, but I did upgrade to 5.1.6 using the standard repository back in October. I was only able to update to 5.2.6 by using the Utter Ramblings repository by Jason Litka. True, I could compile and install 5.3 or the PECL package myself, but I produce software for people who don’t have a clue what a compiler is, let alone what repositories or PECL extensions are.
It’s things like this that make development a pain. I could always just be a jerk of a developer, have the line “requires PHP 5.3+”, and tell all people that can’t run the software to check the requirements. However, I think that my job is to make the end-user’s life easier, not more complicated.
Hopefully others that have had woes dealing with this situation can make use of my code, and we can all just wait a few years until the majority of hosts have a PHP version that supports these calls natively.
Did I help you?
thank you. your code reduced my work.
I’m glad that I could help sure.
Hi!
There are some commonly misdetected types when using fileinfo. These are mostly MS Office ones. Example: docx is basically a ZIP file, so fileinfo will always fail detecting. I completed the list you’re using, and have another one, that has to be used before calling fileinfo to avoid the failure.
Drop me an email, and I’ll send you my list.
Balazs
Sounds good wodka. An email is headed your way shortly.
Thanks. Much appreciated.
You’re welcome montess.
Thanks for sharing your code, I’ve been trying to figure this out for a long time…saved me a lot of workarounds! 😀
mate, you are a life saver – works a treat! 🙂 great for anyone on shared hosting with limited ability to change configuration.
Thanks for sharing.
Outstanding… I’ve been hitting a brick wall getting mime types working after upgrading to PHP 5.3 (still says “undefined function finfo_open()” strangely enough).
Very elegant, portable solution. Thanks!
Your code fails to detect MIME types at all when the file does not have an extension and neither finfo_openn or mime_content_type are available.
How do you propose that?
As noted in the post, PHP’s advanced functions can determine type by content. Reproducing that would be tedious, expensive, and error-prone in pure PHP. The code provided here is to establish a baseline set of code that can be reliably distributed to unknown platforms without fear of catastrophic failure if the system does not support the functions provided by default builds or more recent versions of PHP. In other words, it is a compatibility method combined with a best-effort algorithm.
In the case of this function running without support for the PHP functions for a filename with no matching extension, the last_resort method of the function will return “application/octet-stream”. While this may not be the desired outcome, it will still generate a valid generic MIME type.
Your scenario of a filename without an extension shows a weakness in this approach; however, it should also be noted that a file of type JPEG with an extension of png will receive a MIME type of “image/png” rather than “image/jpeg”. This scenario is potentially worse than the one you brought up as the MIME type in this scenario is wrong whereas your scenario’s result of the “application/octet-stream” MIME type is generic yet valid. Of course if a text file is called “converting.gif.to.jpg”, it would also improperly receive a MIME type of “image/jpeg”.
For my applications, such failings are acceptable as MIME type recognition should always be considered suggestions as even the advanced PHP MIME functions have situations where they identify files properly. If this is unacceptable for your application, you either need to come up with a different solution or rethink your application’s reliance on MIME type identification.
Thank you for this nice workaround! I too was struggling yesterday with determining the mime-type. It’s for a script that sends email attachments. My GoDaddy host runs PHP 5.2.8, so FileInfo was not available, and mime_content_type did not exist either. Another trick I’ve used in the past:
exec(file -bi $file);
Didn’t work either :-/. Apparently the “file” program is not installed on my host… Your workaround solved my problems. Thanks!
Relying on the “file” program is a severe code portability issue. Failure would easily occur if the system does not have the file program, the file program uses a different parameter syntax, or the platform does not support the program (Windows).
[…] http://chrisjean.com/2009/02/14/generating-mime-type-in-php-is-not-magic/ This entry was posted in Uncategorized by admin. Bookmark the permalink. […]
You have made my day. Thank you 😛
Fatal error: Call to undefined function finfo_open() in C:\wamp\www\testing\test.php on line 47
the code in line 47 is: $finfo = finfo_open(FILEINFO_MIME_TYPE);
please help.
Does your comment have anything to do with what I’m talking about in this post, or are you simply looking for someone to help you fix your code?
If you read this post, I talk about why the finfo_open() may not be available on all systems.
Thank you a lot for writing this helpful article.
Have a nice day.
Thanks for the detailed description.
I got into this situation with PHP 5.2.9 and I spend around 4 to 6 hours before reaching upto your blog. You saved my time!
thanks a ton!
Glad that I could help Gaurav.
[…] supply the correct mime type for the file you want to attach to the mail message. Use this article here as a source for some in-depth insights how you can detect the mime-type for a file with a decent […]
Thank you.
Hi,
Thanks for your code. It reduced my time. I’m here by surfing google. I’m agreed with you that its disappointing about PHP’s solution on this regard.
I didn’t test your solution yet. But I think your approach is good.
Thanks again, keep coding.
Regards,
Shahadat
Awesome work chris!! My client was facing issues with older php versions but your script saved me.
Thank you and keep the good work up!
Years later, this is still useful. I was deploying my application to Google App Engine, which doesn’t support any of PHP’s mime type detection. 😛
I’m glad that it was able to help you Steve. Good luck with your code.
I have not try and test your code yet but just let you know a lot of people still searching for a solution for MIME type, in this regard I think the PHP team really have done an unwise action and waste a lot of time ( of we developers).