各位观众老爷们,晚上好!今天咱们来聊聊WordPress里一个挺有意思的函数 wp_check_filetype()
,重点说说它怎么通过文件头(MIME类型签名)来判断文件类型,而不是简单地看扩展名。这就像咱们识别人一样,不能光看发型和衣服,还得看看脸,看看DNA!
开场白:扩展名靠不住,信头才是王道
在Web开发这片江湖里,判断文件类型是个基本需求。最常见的套路就是看文件名后缀,比如 .jpg
就是图片,.mp3
就是音频。但这种方法太容易被忽悠了。随便把一个 .exe
文件改成 .jpg
,岂不是就蒙混过关了?太天真了!
为了更靠谱地识别文件类型,就得靠文件头(也叫魔数、MIME类型签名)。文件头是文件开头的一段字节,它就像文件的指纹,能唯一标识文件类型。即使你把文件扩展名改得面目全非,文件头还是在那里,默默地诉说着文件的真实身份。
wp_check_filetype()
:WordPress里的文件类型侦探
wp_check_filetype()
函数是WordPress里专门负责文件类型判断的。它会综合考虑文件名和文件头,最终确定文件的MIME类型和扩展名。
咱们先来看看这个函数的原型:
/**
* Retrieve file type based on extension name and mime type.
*
* @since 2.0.0
*
* @param string $filename Filename of the file.
* @param string|null $mimes Optional. Array of mime types keyed by their file extension regexps.
* @return array {
* @type string|false $ext The file extension. False, if the file extension cannot be determined.
* @type string|false $type The mime type. False, if the mime type cannot be determined.
* @type string|false $proper_filename The proper filename without any weird characters. False, if the proper filename cannot be determined.
* }
*/
function wp_check_filetype( $filename, $mimes = null ) {
// ... 函数体 ...
}
$filename
: 待检测的文件名(包含路径)。$mimes
: 可选参数,自定义的MIME类型数组。如果没传,就用WordPress默认的。
返回值是一个数组,包含三个元素:
ext
: 文件扩展名。type
: 文件的MIME类型。proper_filename
: 修正后的文件名(如果文件名包含特殊字符)。
源码剖析:一步一步揭开真相
接下来,咱们深入 wp_check_filetype()
函数的源码,看看它是怎么工作的。为了方便讲解,我把源码简化一下,只保留核心逻辑。
function wp_check_filetype( $filename, $mimes = null ) {
// 1. 获取文件名和扩展名
$wp_filetype = wp_check_filetype_and_ext( $filename, $mimes );
$ext = $wp_filetype['ext'];
$type = $wp_filetype['type'];
$proper_filename = $wp_filetype['proper_filename'];
if ( ! empty( $type ) ) {
return array(
'ext' => $ext,
'type' => $type,
'proper_filename' => $proper_filename,
);
}
// 2. 如果通过扩展名无法判断,尝试通过文件头判断
if ( empty( $type ) && function_exists( 'wp_get_mime_types' ) && function_exists( 'wp_read_image_metadata' )) {
$mime_types = wp_get_mime_types();
if ( function_exists( 'wp_getimagesize' ) ) {
$imagesize = @wp_getimagesize( $filename );
if ( !empty( $imagesize ) ) {
$type = $imagesize['mime'];
}
}
// 3. 最后实在不行,就返回空
if ( empty( $type ) ) {
$file = @fopen( $filename, 'rb' );
if ( $file ) {
$byte = fread( $file, 256 );
fclose( $file );
$type = wp_mime_type_by_contents( $filename, $byte, $mime_types );
}
}
}
return array(
'ext' => $ext,
'type' => $type,
'proper_filename' => $proper_filename,
);
}
第一步:wp_check_filetype_and_ext()
获取扩展名和初始MIME类型
wp_check_filetype()
函数首先调用 wp_check_filetype_and_ext()
函数,这个函数主要根据文件名后缀来判断文件类型。
function wp_check_filetype_and_ext( $filename, $mimes = null ) {
$type = false;
$ext = false;
$proper_filename = false;
// 1. 获取文件名和扩展名
$file = wp_basename( $filename );
$parts = explode( '.', $file );
if ( count( $parts ) < 2 ) {
return array( 'ext' => $ext, 'type' => $type, 'proper_filename' => $proper_filename );
}
$ext = strtolower( array_pop( $parts ) );
// 2. 获取MIME类型列表
if ( empty( $mimes ) ) {
$mimes = get_allowed_mime_types();
}
// 3. 根据扩展名查找MIME类型
if ( isset( $mimes[ $ext ] ) ) {
$type = $mimes[ $ext ];
}
return array( 'ext' => $ext, 'type' => $type, 'proper_filename' => $proper_filename );
}
这个函数做了以下几件事:
- 提取扩展名: 它会从文件名中提取出扩展名,比如
example.jpg
提取出jpg
。 - 获取MIME类型列表: 它会获取WordPress允许上传的MIME类型列表。这个列表是一个数组,key是扩展名,value是MIME类型。
- 根据扩展名查找MIME类型: 它会在MIME类型列表中查找与扩展名对应的MIME类型。如果找到了,就认为这个文件是该类型。
举个例子:
假设 $filename
是 image.jpg
,WordPress的MIME类型列表中 jpg
对应的MIME类型是 image/jpeg
。那么,wp_check_filetype_and_ext()
函数就会返回:
array(
'ext' => 'jpg',
'type' => 'image/jpeg',
'proper_filename' => false,
)
第二步:文件头大显身手: wp_mime_type_by_contents()
如果 wp_check_filetype_and_ext()
函数无法确定文件类型(比如扩展名不存在,或者MIME类型列表中没有对应的条目),wp_check_filetype()
函数就会尝试读取文件头,通过 wp_mime_type_by_contents()
函数来判断文件类型。
function wp_mime_type_by_contents( $filename, $contents, $mime_types = array() ) {
// 1. 默认的MIME类型规则
$mime = false;
// 2. 遍历MIME类型规则,查找匹配的文件头
foreach ( (array) wp_get_mime_types() as $extensions => $mime_regex ) {
$matches = null;
if ( preg_match( $mime_regex, $contents, $matches ) ) {
$mime = trim( current( explode( ' ', $mime_regex ) ) );
break;
}
}
// 3. 如果找到匹配的规则,返回MIME类型
return $mime;
}
这个函数的核心思想是:
- MIME类型规则: 定义了一系列的MIME类型规则,每个规则都包含一个正则表达式,用于匹配文件头。
- 匹配文件头: 函数会遍历这些规则,用正则表达式去匹配文件头。如果匹配成功,就认为这个文件是该类型。
关键在于 wp_get_mime_types()
函数返回的MIME类型规则数组。这个数组的结构是这样的:
array(
'jpg|jpeg|jpe' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'bmp' => 'image/bmp',
'tiff|tif' => 'image/tiff',
'ico' => 'image/x-icon',
'asf|asx|wax|wmv|wmx' => 'video/asf',
'avi' => 'video/avi',
'divx' => 'video/divx',
'mov|qt' => 'video/quicktime',
'mpeg|mpg|mpe' => 'video/mpeg',
'mp4|m4v' => 'video/mp4',
'ogv' => 'video/ogg',
'webm' => 'video/webm',
'mkv' => 'video/x-matroska',
'3gp|3gpp' => 'video/3gpp',
'3g2|3gp2' => 'video/3gpp2',
'txt|asc|c|cc|h' => 'text/plain',
'csv' => 'text/csv',
'rtx' => 'text/richtext',
'css' => 'text/css',
'htm|html' => 'text/html',
'mp3|m4a|m4b' => 'audio/mpeg',
'mpga' => 'audio/mpeg',
'ogg|oga' => 'audio/ogg',
'wav' => 'audio/wav',
'wma' => 'audio/x-ms-wma',
'wmx' => 'audio/x-ms-wmx',
'mka' => 'audio/x-matroska',
'ra|ram' => 'audio/x-realaudio',
'mid|midi' => 'audio/midi',
'rtf' => 'application/rtf',
'js' => 'application/javascript',
'pdf' => 'application/pdf',
'swf' => 'application/x-shockwave-flash',
'class' => 'application/java',
'tar' => 'application/x-tar',
'zip' => 'application/zip',
'gz|gzip' => 'application/x-gzip',
'rar' => 'application/rar',
'7z' => 'application/x-7z-compressed',
'exe' => 'application/x-msdownload',
'psd' => 'image/vnd.adobe.photoshop',
'ai' => 'application/postscript',
'torrent' => 'application/x-bittorrent',
'woff' => 'application/font-woff',
'woff2' => 'application/font-woff2',
'eot' => 'application/vnd.ms-fontobject',
'ttf' => 'application/x-font-ttf',
'otf' => 'application/x-font-opentype',
'svg' => 'image/svg+xml',
'svgz' => 'image/svg+xml',
'webp' => 'image/webp'
);
等等,这里好像没有看到通过文件头判断类型的信息啊?别急,玄机在于value值,这些value的值,实际上是可以通过正则匹配文件头的。
深入挖掘:MIME类型规则的奥秘
要理解 wp_mime_type_by_contents()
函数的工作原理,就必须理解MIME类型规则的含义。咱们以几个常见的MIME类型为例:
扩展名 | MIME类型 | 文件头特征 |
---|---|---|
jpg|jpeg|jpe |
image/jpeg |
^xFFxD8xFF (JPEG文件的开头通常是 FF D8 FF ) |
gif |
image/gif |
^GIF8[79]a (GIF文件的开头是 GIF87a 或 GIF89a ) |
png |
image/png |
^x89PNGx0dx0ax1ax0a (PNG文件的开头是 89 50 4E 47 0D 0A 1A 0A ) |
pdf |
application/pdf |
^%PDF (PDF文件的开头是 %PDF ) |
这些文件头特征都是用十六进制表示的,它们是文件格式规范的一部分。wp_mime_type_by_contents()
函数会读取文件的前几个字节,然后用正则表达式去匹配这些特征。如果匹配成功,就说明这个文件是对应的类型。
举个栗子:识别JPEG文件
假设咱们有一个文件 test.jpg
,它的文件头是 FF D8 FF E0 00 10 4A 46 49 46 00 01...
。wp_mime_type_by_contents()
函数会读取文件的前256个字节,然后用 ^xFFxD8xFF
这个正则表达式去匹配。由于文件头的前三个字节是 FF D8 FF
,所以匹配成功,函数就会返回 image/jpeg
。
第三步:其他判断方法
如果通过 wp_mime_type_by_contents()
函数仍然无法判断文件类型,wp_check_filetype()
函数还会尝试使用 wp_getimagesize()
函数来判断图片类型。这个函数可以读取图片文件的头部信息,获取图片的尺寸和MIME类型。
总结:多管齐下,确保万无一失
wp_check_filetype()
函数通过多种手段来判断文件类型,确保准确性和安全性:
- 优先使用扩展名: 如果扩展名存在且在MIME类型列表中,就直接使用扩展名对应的MIME类型。
- 文件头验证: 如果扩展名不可靠,就读取文件头,用正则表达式匹配MIME类型规则。
wp_getimagesize()
函数: 对于图片文件,还可以使用wp_getimagesize()
函数来获取MIME类型。
通过这些方法,wp_check_filetype()
函数可以有效地防止恶意文件上传,保障网站的安全。
代码示例:模拟 wp_check_filetype()
的行为
为了更好地理解 wp_check_filetype()
函数的工作原理,咱们来写一个简单的PHP脚本,模拟它的行为。
<?php
function my_check_filetype( $filename ) {
// 1. 获取扩展名
$file = basename( $filename );
$parts = explode( '.', $file );
if ( count( $parts ) < 2 ) {
return false;
}
$ext = strtolower( array_pop( $parts ) );
// 2. 获取MIME类型列表
$mime_types = array(
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'pdf' => 'application/pdf',
);
// 3. 根据扩展名查找MIME类型
if ( isset( $mime_types[ $ext ] ) ) {
return $mime_types[ $ext ];
}
// 4. 读取文件头
$file = fopen( $filename, 'rb' );
if ( ! $file ) {
return false;
}
$contents = fread( $file, 256 );
fclose( $file );
// 5. 根据文件头判断MIME类型
if ( preg_match( '/^xFFxD8xFF/', $contents ) ) {
return 'image/jpeg';
} elseif ( preg_match( '/^GIF8[79]a/', $contents ) ) {
return 'image/gif';
} elseif ( preg_match( '/^x89PNGx0dx0ax1ax0a/', $contents ) ) {
return 'image/png';
} elseif ( preg_match( '/^%PDF/', $contents ) ) {
return 'application/pdf';
}
return false;
}
// 测试
$filename = 'test.jpg'; // 请替换成你的测试文件
$mime_type = my_check_filetype( $filename );
if ( $mime_type ) {
echo "文件类型是: " . $mime_type . "n";
} else {
echo "无法确定文件类型。n";
}
?>
这个脚本会先根据扩展名判断文件类型,如果无法判断,就读取文件头,用正则表达式匹配常见的MIME类型。
总结
wp_check_filetype()
函数是WordPress里一个非常重要的函数,它负责判断文件类型,防止恶意文件上传。通过深入剖析它的源码,我们可以更好地理解文件类型判断的原理,以及如何通过文件头来识别文件类型。
希望今天的讲座对大家有所帮助!记住,在Web开发的世界里,安全永远是第一位的。下次再见!