protected function _file_mime_type($file)
{
// We'll need this to validate the MIME info string (e.g. text/plain; charset=us-ascii)
$regexp = '/^([a-z\\-]+\\/[a-z0-9\\-\\.\\+]+)(;\\s.+)?$/';
/* Fileinfo extension - most reliable method
*
* Unfortunately, prior to PHP 5.3 - it's only available as a PECL extension and the
* more convenient FILEINFO_MIME_TYPE flag doesn't exist.
*/
if (function_exists('finfo_file')) {
$finfo = @finfo_open(FILEINFO_MIME);
if (is_resource($finfo)) {
$mime = @finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
/* According to the comments section of the PHP manual page,
* it is possible that this function returns an empty string
* for some files (e.g. if they don't exist in the magic MIME database)
*/
if (is_string($mime) && preg_match($regexp, $mime, $matches)) {
$this->file_type = $matches[1];
return;
}
}
}
/* This is an ugly hack, but UNIX-type systems provide a "native" way to detect the file type,
* which is still more secure than depending on the value of $_FILES[$field]['type'], and as it
* was reported in issue #750 (https://github.com/EllisLab/CodeIgniter/issues/750) - it's better
* than mime_content_type() as well, hence the attempts to try calling the command line with
* three different functions.
*
* Notes:
* - the DIRECTORY_SEPARATOR comparison ensures that we're not on a Windows system
* - many system admins would disable the exec(), shell_exec(), popen() and similar functions
* due to security concerns, hence the function_usable() checks
*/
if (DIRECTORY_SEPARATOR !== '\\') {
$cmd = function_exists('escapeshellarg') ? 'file --brief --mime ' . escapeshellarg($file['tmp_name']) . ' 2>&1' : 'file --brief --mime ' . $file['tmp_name'] . ' 2>&1';
if (function_usable('exec')) {
/* This might look confusing, as $mime is being populated with all of the output when set in the second parameter.
* However, we only need the last line, which is the actual return value of exec(), and as such - it overwrites
* anything that could already be set for $mime previously. This effectively makes the second parameter a dummy
* value, which is only put to allow us to get the return status code.
*/
$mime = @exec($cmd, $mime, $return_status);
if ($return_status === 0 && is_string($mime) && preg_match($regexp, $mime, $matches)) {
$this->file_type = $matches[1];
return;
}
}
if (!ini_get('safe_mode') && function_usable('shell_exec')) {
$mime = @shell_exec($cmd);
if (strlen($mime) > 0) {
$mime = explode("\n", trim($mime));
if (preg_match($regexp, $mime[count($mime) - 1], $matches)) {
$this->file_type = $matches[1];
return;
}
}
}
if (function_usable('popen')) {
$proc = @popen($cmd, 'r');
if (is_resource($proc)) {
$mime = @fread($proc, 512);
@pclose($proc);
if ($mime !== FALSE) {
$mime = explode("\n", trim($mime));
if (preg_match($regexp, $mime[count($mime) - 1], $matches)) {
$this->file_type = $matches[1];
return;
}
}
}
}
}
// Fall back to the deprecated mime_content_type(), if available (still better than $_FILES[$field]['type'])
if (function_exists('mime_content_type')) {
$this->file_type = @mime_content_type($file['tmp_name']);
if (strlen($this->file_type) > 0) {
return;
}
}
$this->file_type = $file['type'];
}