-
学习PHP中Fileinfo扩展的使用
今天来学习的这个扩展其实现在也已经是标配的一个扩展了,为什么呢?因为 Laravel 框架在安装的时候它就是必须的一个扩展,没有打开它的话,连 Laravel 框架都是无法使用的。
Fileinfo 介绍
Fileinfo 是通过给定的 magic 字节序列库来获得文件的内容类型及编码。它所获取的序列库是根据操作系统来定的,比如在 Linux 系统中默认使用的就是 /usr/share/misc/magic 这个文件。其实我们就是可以用这个扩展的函数来获得文件的 MIME 信息,就像我们常见的 image/png 、 text/html 这些内容。
Fileinfo 这个扩展也是为了兼容新老开发模式,所以提供了面向过程和面向对象的两种形式,我们先来看看面向对象的形式如何使用。
面向对象使用
$finfo = new finfo(FILEINFO_MIME);
echo $finfo->file("./1.PHP中的日期相关函数(三).php"), PHP_EOL;
// text/x-php; charset=us-ascii
echo $finfo->buffer(file_get_contents("https://www.baidu.com")) . "\n";
// text/html; charset=utf-8
$finfo->set_flags(FILEINFO_EXTENSION);
echo $finfo->file('timg.jpeg') . "\n";
// jpeg/jpg/jpe/jfif
首先,我们通过 new 一个 finfo 类来获得文件操作对象,参数中的常量是可选的,默认情况下是 FILEINFO_NONE ,代表无特殊处理,这里我们使用的是 FILEINFO_MIME 表明按照 RFC2045 定义的格式返回文件 mime 类型和编码。
然后使用 file() 方法,就可以获得指定文件的 mime 信息了。buffer() 方法则是返回字符串内容的信息,比如我们获取一个网页信息的内容,就能够得到它的字符串代表的文件编码格式为 text/hmtl 。set_flags() 方法是可以在实例化对象之后修改它的构造参数属性,也就是我们在实例化时设置的那个参数信息,在这里我们将它修改为 FILEINFO_EXTENSION ,也就是让 finfo 对象返回文件可能的扩展名。我们使用一张图片进行测试,返回的可能扩展名就包括注释中展示的这些。
面向过程
针对上面面向对象的代码,我们也展示一下相同的操作使用面向过程的函数是如何运行的。
$finfo = finfo_open(FILEINFO_MIME);
echo finfo_file($finfo,"./1.PHP中的日期相关函数(三).php"), PHP_EOL;
// text/x-php; charset=us-ascii
echo finfo_buffer($finfo, file_get_contents("https://www.baidu.com")), PHP_EOL;
// text/html; charset=utf-8
finfo_set_flags($finfo, FILEINFO_EXTENSION);
echo finfo_file($finfo, 'timg.jpeg') . "\n";
// jpeg/jpg/jpe/jfif
finfo_close($finfo);
可以看出,这里就是将 finfo 对象换成了 finfo_open() 方法来获得一个 finfo 操作句柄。之后使用类似的 finfo_file() 、 finfo_buffer() 、 finfo_set_flags() 函数来进行操作,实现的效果和上面的面向对象的结果是一样的。
需要注意的,面向过程的写法是有一个 finfo_close() 方法的,一般对于句柄类型的操作都会有一个关闭的函数来释放句柄资源。就像 mysqli 之类的扩展一样,finfo 中也是包含一个这样的函数的,并且只提供面向过程的这个函数,上面的 finfo 类中是没有这样一个 close() 方法的。
快速返回 mime
当然,Fileinfo 扩展也为我们提供了一个快速地返回文件 mime 信息的函数。我们可以不用使用 finfo 对象或者打开一个 finfo 句柄就可以方便快捷地获得一个文件的 mime 信息。
echo mime_content_type('./1.PHP中的日期相关函数(三).php'), PHP_EOL;
// text/x-php
echo mime_content_type('./timg.jpeg'), PHP_EOL;
// image/jpeg
不过 PHP 官方似乎废弃过这个函数,但现在又恢复了它,也就是说并不是特别的推荐使用这个函数。在正式的开发过程中,我们还是不要嫌麻烦,使用 finfo 对象或者 finfo 相关的函数来获得 mime 信息会更靠谱一些。
如果我们确定要判断的文件只是图片类型的话,那么我们还可以用另外一个函数来进行图片文件的 mime 获取。
$image = exif_imagetype("./timg.jpeg");
echo image_type_to_mime_type($image), PHP_EOL;
// image/jpeg
总结
非常简单但是很实用的函数,为什么说它实用呢?上传文件的安全性问题就可以靠它来解决。我们在上传文件的时候,通常会判断文件的后缀名及上传数组中的文件 MIME 类型。不过很多工具是可以在上传过程中修改文件的 MIME 类型的,也就是通过一些抓包工具修改 Content-Type 。而通过 Fileinfo 扩展获得必须是本地或远程已经存在的文件,也就是说不会有上传过程中因为修改传输信息而产生的安全检查绕过问题。
因此,在 Lavarl 框架中,vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php 类中的 mimeType() 方法使用的正是 finfo_file() 这个函数来获取文件的 mime 信息。在它的上传组件中,Laravel 的底层 symfony 框架中,对于上传文件的 MIME 判断也是使用的 finfo_file() 函数,(vendor/symfony/mime/FileinfoMimeTypeGuesser.php) 并没有直接使用正常的上传后的 $_FILES 中的 type 字段。
测试代码: