WordPress Version: 6.5
/**
* Attempts to determine the real file type of a file.
*
* If unable to, the file name extension will be used to determine type.
*
* If it's determined that the extension does not match the file's real type,
* then the "proper_filename" value will be set with a proper filename and extension.
*
* Currently this function only supports renaming images validated via wp_get_image_mime().
*
* @since 3.0.0
*
* @param string $file Full path to the file.
* @param string $filename The name of the file (may differ from $file due to $file being
* in a tmp directory).
* @param string[]|null $mimes Optional. Array of allowed mime types keyed by their file extension regex.
* Defaults to the result of get_allowed_mime_types().
* @return array {
* Values for the extension, mime type, and corrected filename.
*
* @type string|false $ext File extension, or false if the file doesn't match a mime type.
* @type string|false $type File mime type, or false if the file doesn't match a mime type.
* @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined.
* }
*/
function wp_check_filetype_and_ext($file, $filename, $mimes = null)
{
$proper_filename = false;
// Do basic extension validation and MIME mapping.
$wp_filetype = wp_check_filetype($filename, $mimes);
$ext = $wp_filetype['ext'];
$type = $wp_filetype['type'];
// We can't do any further validation without a file to work with.
if (!file_exists($file)) {
return compact('ext', 'type', 'proper_filename');
}
$real_mime = false;
// Validate image types.
if ($type && str_starts_with($type, 'image/')) {
// Attempt to figure out what type of image it actually is.
$real_mime = wp_get_image_mime($file);
if ($real_mime && $real_mime !== $type) {
/**
* Filters the list mapping image mime types to their respective extensions.
*
* @since 3.0.0
*
* @param array $mime_to_ext Array of image mime types and their matching extensions.
*/
$mime_to_ext = apply_filters('getimagesize_mimes_to_exts', array('image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/bmp' => 'bmp', 'image/tiff' => 'tif', 'image/webp' => 'webp', 'image/avif' => 'avif'));
// Replace whatever is after the last period in the filename with the correct extension.
if (!empty($mime_to_ext[$real_mime])) {
$filename_parts = explode('.', $filename);
array_pop($filename_parts);
$filename_parts[] = $mime_to_ext[$real_mime];
$new_filename = implode('.', $filename_parts);
if ($new_filename !== $filename) {
$proper_filename = $new_filename;
// Mark that it changed.
}
// Redefine the extension / MIME.
$wp_filetype = wp_check_filetype($new_filename, $mimes);
$ext = $wp_filetype['ext'];
$type = $wp_filetype['type'];
} else {
// Reset $real_mime and try validating again.
$real_mime = false;
}
}
}
// Validate files that didn't get validated during previous checks.
if ($type && !$real_mime && extension_loaded('fileinfo')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$real_mime = finfo_file($finfo, $file);
finfo_close($finfo);
$google_docs_types = array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
foreach ($google_docs_types as $google_docs_type) {
/*
* finfo_file() can return duplicate mime type for Google docs,
* this conditional reduces it to a single instance.
*
* @see https://bugs.php.net/bug.php?id=77784
* @see https://core.trac.wordpress.org/ticket/57898
*/
if (2 === substr_count($real_mime, $google_docs_type)) {
$real_mime = $google_docs_type;
}
}
// fileinfo often misidentifies obscure files as one of these types.
$nonspecific_types = array('application/octet-stream', 'application/encrypted', 'application/CDFV2-encrypted', 'application/zip');
/*
* If $real_mime doesn't match the content type we're expecting from the file's extension,
* we need to do some additional vetting. Media types and those listed in $nonspecific_types are
* allowed some leeway, but anything else must exactly match the real content type.
*/
if (in_array($real_mime, $nonspecific_types, true)) {
// File is a non-specific binary type. That's ok if it's a type that generally tends to be binary.
if (!in_array(substr($type, 0, strcspn($type, '/')), array('application', 'video', 'audio'), true)) {
$type = false;
$ext = false;
}
} elseif (str_starts_with($real_mime, 'video/') || str_starts_with($real_mime, 'audio/')) {
/*
* For these types, only the major type must match the real value.
* This means that common mismatches are forgiven: application/vnd.apple.numbers is often misidentified as application/zip,
* and some media files are commonly named with the wrong extension (.mov instead of .mp4)
*/
if (substr($real_mime, 0, strcspn($real_mime, '/')) !== substr($type, 0, strcspn($type, '/'))) {
$type = false;
$ext = false;
}
} elseif ('text/plain' === $real_mime) {
// A few common file types are occasionally detected as text/plain; allow those.
if (!in_array($type, array('text/plain', 'text/csv', 'application/csv', 'text/richtext', 'text/tsv', 'text/vtt'), true)) {
$type = false;
$ext = false;
}
} elseif ('application/csv' === $real_mime) {
// Special casing for CSV files.
if (!in_array($type, array('text/csv', 'text/plain', 'application/csv'), true)) {
$type = false;
$ext = false;
}
} elseif ('text/rtf' === $real_mime) {
// Special casing for RTF files.
if (!in_array($type, array('text/rtf', 'text/plain', 'application/rtf'), true)) {
$type = false;
$ext = false;
}
} else if ($type !== $real_mime) {
/*
* Everything else including image/* and application/*:
* If the real content type doesn't match the file extension, assume it's dangerous.
*/
$type = false;
$ext = false;
}
}
// The mime type must be allowed.
if ($type) {
$allowed = get_allowed_mime_types();
if (!in_array($type, $allowed, true)) {
$type = false;
$ext = false;
}
}
/**
* Filters the "real" file type of the given file.
*
* @since 3.0.0
* @since 5.1.0 The $real_mime parameter was added.
*
* @param array $wp_check_filetype_and_ext {
* Values for the extension, mime type, and corrected filename.
*
* @type string|false $ext File extension, or false if the file doesn't match a mime type.
* @type string|false $type File mime type, or false if the file doesn't match a mime type.
* @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined.
* }
* @param string $file Full path to the file.
* @param string $filename The name of the file (may differ from $file due to
* $file being in a tmp directory).
* @param string[]|null $mimes Array of mime types keyed by their file extension regex, or null if
* none were provided.
* @param string|false $real_mime The actual mime type or false if the type cannot be determined.
*/
return apply_filters('wp_check_filetype_and_ext', compact('ext', 'type', 'proper_filename'), $file, $filename, $mimes, $real_mime);
}