1、可接受上传的文件扩展名列表:ogg, pdf, xml, zip, gz, mp4, mp3, wav, webm, gif, jpeg, jpg, png, webp, svg, svgz, tiff, css, csv, txt, vcf, vcard, mov, qt, mkv, mk3d, mka, mks, wmv, flv, doc

2、上传扩展名为:.doc 的文件,上传成功,如图1

图1

{
    "code": 10000,
    "message": "上传资源成功",
    "data": {
        "items": [
            {
                "original_file_name": "原始凭证单据(1).doc",
                "relative_path": "/tmp/2019/12/20/1576810484.4368.436639923.doc",
                "url": "http://127.0.0.1/pcs-api/storage/tmp/2019/12/20/1576810484.4368.436639923.doc"
            }
        ]
    }
}

3、上传扩展名为:.docx 的文件,上传失败,提示:不允许,如图2

图2

{
    "code": 226004,
    "message": "数据验证失败:只允许使用以下文件扩展名的文件:ogg, pdf, xml, zip, gz, mp4, mp3, wav, webm, gif, jpeg, jpg, png, webp, svg, svgz, tiff, css, csv, txt, vcf, vcard, mov, qt, mkv, mk3d, mka, mks, wmv, flv, doc, docx。"
}

4、编辑 Yii 2.0 框架的核心文件:validators/FileValidator.php,修改方法:validateExtension($file),以启动调试,再次请求,输出:2,如图3

图3

    /**
     * Checks if given uploaded file have correct type (extension) according current validator settings.
     * @param UploadedFile $file
     * @return bool
     */    protected function validateExtension($file)
    {
        $extension = mb_strtolower($file->extension, 'UTF-8');

        if ($this->checkExtensionByMimeType) {
            $mimeType = FileHelper::getMimeType($file->tempName, null, false);
            if ($mimeType === null) {
                echo 1;
                exit;
                return false;
            }

            $extensionsByMimeType = FileHelper::getExtensionsByMimeType($mimeType);

            if (!in_array($extension, $extensionsByMimeType, true)) {
                echo 2;
                exit;
                return false;
            }
        }

        if (!in_array($extension, $this->extensions, true)) {
            echo 3;
            exit;
            return false;
        }

        return true;
    }

5、checkExtensionByMimeType:是否通过文件的 MIME 类型来判断其文件扩展。若由 MIME 判定的文件扩展与给定文件的扩展不一样,则文件会被认为无效。默认为 true,代表执行上述检测。依次打印输出:$mimeType、$extensionsByMimeType、$extension,其值分别为:

application/zip

Array
(
    [0] => zip
)

docx

6、分析结果,由于:checkExtensionByMimeType 未明确设置,默认为 true,由 MIME 判定的文件扩展(zip)与给定文件的扩展(docx)不一样,则文件会被认为无效。编辑 Yii 2.0 框架的核心文件:helpers/BaseFileHelper.php,修改方法:getMimeType,以启动调试,依次打印输出:$file、$result,其值分别为:

    /**
     * Determines the MIME type of the specified file.
     * This method will first try to determine the MIME type based on
     * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If the `fileinfo` extension is not installed,
     * it will fall back to [[getMimeTypeByExtension()]] when `$checkExtension` is true.
     * @param string $file the file name.
     * @param string $magicFile name of the optional magic database file (or alias), usually something like `/path/to/magic.mime`.
     * This will be passed as the second parameter to [finfo_open()](http://php.net/manual/en/function.finfo-open.php)
     * when the `fileinfo` extension is installed. If the MIME type is being determined based via [[getMimeTypeByExtension()]]
     * and this is null, it will use the file specified by [[mimeMagicFile]].
     * @param bool $checkExtension whether to use the file extension to determine the MIME type in case
     * `finfo_open()` cannot determine it.
     * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined.
     * @throws InvalidConfigException when the `fileinfo` PHP extension is not installed and `$checkExtension` is `false`.
     */    public static function getMimeType($file, $magicFile = null, $checkExtension = true)
    {
        if ($magicFile !== null) {
            $magicFile = Yii::getAlias($magicFile);
        }
        if (!extension_loaded('fileinfo')) {
            if ($checkExtension) {
                return static::getMimeTypeByExtension($file, $magicFile);
            }

            throw new InvalidConfigException('The fileinfo PHP extension is not installed.');
        }
        $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile);

        if ($info) {
            $result = finfo_file($info, $file);
            finfo_close($info);

            if ($result !== false) {
                print_r($file);
                echo "\n";
                echo "\n";
                print_r($result);
                exit;
                return $result;
            }
        }

        return $checkExtension ? static::getMimeTypeByExtension($file, $magicFile) : null;
    }
E:\phpuploadtmp\phpF3BE.tmp

application/zip

7、PHP 7.2 语言本身的 Fileinfo 函数:finfo_file,对于 .docx 的文件无法准确识别(感觉上),实则 .docx 是一种压缩的 xml 格式,这就是为什么 file_info() 返回 application_zip 的原因(完全正确)。新建文件:finfo_file.php,输出:application/zip,如图4

图4

<?php
$info = finfo_open(FILEINFO_MIME_TYPE, null);

if ($info) {
 $result = finfo_file($info, 'D:\华栖云\原始凭证单据(1).docx');
 finfo_close($info);

 if ($result !== false) {
  echo $result;
 }
}
?>

8、参考官方文档:https://www.php.net/manual/zh/function.finfo-file.php ,其中存在 MS Office 2007 扩展(pptx,xlsx,docx) 没有默认的 Mime 类型,它们具有 application/zip Mime 类型的解决方案,本质上,做了一下特殊处理,以安全地防范假扩展。如图5

图5

9、新建类文件:common/logics/Upload.php,继承至 \yii\validators\FileValidator,以覆写验证扩展名的方法:validateExtension($file)

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/12/20
 * Time: 14:28
 */
namespace common\components\validators;

use yii\base\InvalidConfigException;
use yii\helpers\FileHelper;
use yii\web\UploadedFile;

/**
 * FileValidator verifies if an attribute is receiving a valid uploaded file.
 *
 * Note that you should enable `fileinfo` PHP extension.
 *
 * @property int $sizeLimit The size limit for uploaded files. This property is read-only.
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */class FileValidator extends \yii\validators\FileValidator
{
    /**
     * Checks if given uploaded file have correct type (extension) according current validator settings.
     * @param UploadedFile $file
     * @return bool
     * @throws InvalidConfigException when the `fileinfo` PHP extension is not installed and `$checkExtension` is `false`.
     */    protected function validateExtension($file)
    {
        $extension = mb_strtolower($file->extension, 'UTF-8');

        if ($this->checkExtensionByMimeType) {
            $mimeType = FileHelper::getMimeType($file->tempName, null, false);
            if ($mimeType === null) {
                return false;
            }

            $extensionsByMimeType = FileHelper::getExtensionsByMimeType($mimeType);

            if (!in_array($extension, $extensionsByMimeType, true)) {
                // MS Office 2007 扩展(docx、xlsx),其 MIME 类型为 application/zip 的特殊处理
                $msMimeTypes = ['application/zip'];
                $msExtensions = ['docx', 'xlsx'];
                if (!(in_array($mimeType, $msMimeTypes) && in_array($extension, $msExtensions)))
                {
                    return false;
                }
            }
        }

        if (!in_array($extension, $this->extensions, true)) {
            return false;
        }

        return true;
    }
}
docx
application/zip

xlsx
application/zip

pptx
application/vnd.openxmlformats-officedocument.presentationml.presentation

10、上传模型的验证规则,之前是验证器别名:file,现在调整为:common\components\validators\FileValidator

    public function rules()
    {
        return [
            [['files'], 'file', 'skipOnEmpty' => false, 'extensions' => Yii::$app->params['pcsApi']['asset']['upload']['extensions'], 'mimeTypes' => Yii::$app->params['pcsApi']['asset']['upload']['mimeTypes'], 'minSize' => Yii::$app->params['pcsApi']['asset']['upload']['minSize'], 'maxSize' => Yii::$app->params['pcsApi']['asset']['upload']['maxSize'], 'maxFiles' => Yii::$app->params['pcsApi']['asset']['upload']['maxFiles']],
        ];
    }
    public function rules()
    {
        return [
            [['files'], 'common\components\validators\FileValidator', 'skipOnEmpty' => false, 'extensions' => Yii::$app->params['pcsApi']['asset']['upload']['extensions'], 'mimeTypes' => Yii::$app->params['pcsApi']['asset']['upload']['mimeTypes'], 'minSize' => Yii::$app->params['pcsApi']['asset']['upload']['minSize'], 'maxSize' => Yii::$app->params['pcsApi']['asset']['upload']['maxSize'], 'maxFiles' => Yii::$app->params['pcsApi']['asset']['upload']['maxFiles']],
        ];
    }

11、可接受上传的文件扩展名列表:ogg, pdf, xml, zip, gz, mp4, mp3, wav, webm, gif, jpeg, jpg, png, webp, svg, svgz, tiff, css, csv, txt, vcf, vcard, mov, qt, mkv, mk3d, mka, mks, wmv, flv, doc, docx, xls, xlsx, ppt, pptx。可接受上传的 MIME 类型列表:application/ogg, application/pdf, application/xml, application/zip, application/gzip, audio/mp4, audio/mpeg, audio/ogg, audio/vnd.wave, audio/webm, image/gif, image/jpeg, image/png, image/webp, image/svg+xml, image/tiff, text/css, text/csv, text/plain, text/vcard, text/xml, video/mpeg, video/mp4, video/ogg, video/quicktime, video/webm, video/x-matroska, video/x-ms-wmv, video/x-flv, application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation。分别上传:docx, xlsx, pptx,皆上传成功,符合预期,如图6

图6

{
    "code": 10000,
    "message": "上传资源成功",
    "data": {
        "items": [
            {
                "original_file_name": "原始凭证单据(1).docx",
                "relative_path": "/tmp/2019/12/20/1576825976.9466.2119963527.docx",
                "url": "http://127.0.0.1/pcs-api/storage/tmp/2019/12/20/1576825976.9466.2119963527.docx"
            }
        ]
    }
}
{
    "code": 10000,
    "message": "上传资源成功",
    "data": {
        "items": [
            {
                "original_file_name": "周报模板.xlsx",
                "relative_path": "/tmp/2019/12/20/1576826044.1977.1865556442.xlsx",
                "url": "http://127.0.0.1/pcs-api/storage/tmp/2019/12/20/1576826044.1977.1865556442.xlsx"
            }
        ]
    }
}
{
    "code": 10000,
    "message": "上传资源成功",
    "data": {
        "items": [
            {
                "original_file_name": "邮箱使用说明.pptx",
                "relative_path": "/tmp/2019/12/20/1576826075.8429.300942829.pptx",
                "url": "http://127.0.0.1/pcs-api/storage/tmp/2019/12/20/1576826075.8429.300942829.pptx"
            }
        ]
    }
}

 

永夜