当 cakephp 4 中没有文件上传时避免 mimeType 验证

Posted

技术标签:

【中文标题】当 cakephp 4 中没有文件上传时避免 mimeType 验证【英文标题】:Avoid mimeType validation when no file is uploaded in cakephp 4 【发布时间】:2021-12-22 22:21:16 【问题描述】:

这是我的输入文件“图像”的验证:

public function validationDefault(Validator $validator): Validator

    $validator = parent::validationDefault($validator);

    $validator
        ->allowEmptyFile('image')
        ->add('image', 'uploadError', [
            'rule' => function ($value, $context) 
                foreach ($value as $v) 
                    return Validation::uploadError($v, true);
                
            ,
            'last' => true,
            'message' => 'Upload error'
        ])
        ->add('image', 'mimeType', [
            'rule' => function ($value, $context) 
                foreach ($value as $v) 
                    return Validation::mimeType($v, [
                        'image/png',
                        'image/gif',
                        'image/pjpeg',
                        'image/jpeg'
                    ]);
                
            ,
            'message' => 'Bad mime type.',
        ]);


提交文件时效果很好,但是当没有文件上传时触发mimeType验证错误。

所以我修改了 mimeType 规则,以在检查 mimeType 之前检查文件是否已上传:

public function validationDefault(Validator $validator): Validator

    $validator = parent::validationDefault($validator);

    $validator
        ->allowEmptyFile('image')
        ->add('image', 'uploadError', [
            'rule' => function ($value, $context) 
                foreach ($value as $v) 
                    return Validation::uploadError($v, true);
                
            ,
            'last' => true,
            'message' => 'Upload error'
        ])
        ->add('image', 'mimeType', [
            'rule' => function ($value, $context) 

                // Added to avoid mimeType validation when no file is uploaded
                if ($value[0]->getError() === UPLOAD_ERR_NO_FILE) 
                    return true;
                

                foreach ($value as $v) 
                    return Validation::mimeType($v, [
                        'image/png',
                        'image/gif',
                        'image/pjpeg',
                        'image/jpeg'
                    ]);
                
            ,
            'message' => 'Bad mime type.',
        ]);


它有效,但对我来说添加起来似乎不太干净 if ($value[0]->getError() === UPLOAD_ERR_NO_FILE) return true; 在 mime 类型检查之后可以添加的每条规则(例如,我将添加文件大小检查、图像宽度检查等)

有没有更好的方法在提交文件时才在文件上添加验证规则?

【问题讨论】:

为什么值是一个数组?单次上传不应该是这种情况。如果你有一个多上传字段,那么验证就会出错。 该值是一个数组,因为我已将输入设置为:echo $this->Form->control('image', ['type' => 'file', 'name' => 'image[]']);。我正在创建一个用于文件管理的插件,其附件行为使用 hasMany 关联链接文件。与其根据多上传文件管理 hasOne 和 hasMany 关联,我认为如果我只管理 hasMany 关联,即使是单个上传文件,我的代码也会更清晰。 @ndm 确实,当 $value 不是数组时,我不需要添加 if ($value[0]->getError() === UPLOAD_ERR_NO_FILE) return true;。但是多上传输入文件呢?如何验证? 这不是我想要建议的,事实上,我此时并没有提出任何建议的意思,我只是想知道您的设置,因为您的问题的可能解决方案可能取决于它,并且因为验证看起来是错误的,因为它只检查数组中的第一个条目,这可能意味着对于多文件输入,未验证的文件可能会漏掉。 @ndm 在多上传输入的情况下,该值是一个数组。无论如何$value 总是至少有一个条目:如果没有上传文件$value 有一个条目[0] UPLOAD_ERR_NO_FILE 有错误,如果有一个或多个文件上传了第一个条目(文件)可以' UPLOAD_ERR_NO_FILE 上没有错误。但是在我添加到字段中的每个验证规则中检查if ($value[0]->getError() === UPLOAD_ERR_NO_FILE) return true; 对我来说似乎很难看。 【参考方案1】:

查看您的上传功能当前的结构,即所有上传都存储在同一个表中,行为通过hasMany 关联处理它们,包括仅接受单个文件的模型,以及使用多文件的前端-file 输入,一种可能的解决方案是在上传空文件的情况下简单地删除该字段。

您可以通过Model.beforeMarshal事件/回调更改数据,它将在创建/修补实体时运行,在应用验证之前,例如:

public function beforeMarshal(
    \Cake\Event\EventInterface $event,
    \ArrayAccess $data,
    \ArrayObject $options
): void 
    if (
        isset($data['image'][0]) &&
        $data['image'][0] instanceof \Psr\Http\Message\UploadedFileInterface &&
        $data['image'][0]->getError() === \UPLOAD_ERR_NO_FILE
    ) 
        unset($data['image']);
    

这将删除image 字段,以防它是一个数组,并且第一个元素是一个空的上传文件对象。这是未选择文件的多文件输入以及使用数组作为名称的单文件输入所收到的内容。

当该字段不再存在时,您的验证规则将根本不运行,并且当它们确实运行时,它们不必允许这种情况,而是可以严格要求有效的上传文件对象。

因此,例如对于应该只接受单次上传的模型,您可以执行以下操作来确保该值是一个数组,该数组只包含一个带有上传文件对象的元素:

$validator
    ->add('image', 'exactlyOneUploadedFile', [
        'rule' => function ($value, $context) 
            if (
                is_array($value) &&
                count($value) === 1 &&
                $value[0] instanceof \Psr\Http\Message\UploadedFileInterface
            ) 
                return true;
            

            return false;
        ,
        // ...
    ])
    // ...

同样,对于应该接受多次上传的模型,您可以执行以下操作来确保该值是一个数组,其中包含仅上传文件对象的一个​​或多个元素:

$validator
    ->add('image', 'onlyUploadedFiles', [
        'rule' => function ($value, $context) 
            if (
                !is_array($value) ||
                count($value) < 1
            ) 
                return false;
            

            foreach ($value as $upload) 
                if (!($upload instanceof \Psr\Http\Message\UploadedFileInterface)) 
                    return false;
                
            

            return true;
        ,
        // ...
    ])
    // ...

您必须严格执行这些检查,这一点很重要!例如,您发布的验证规则将仅检查数组中的第一个条目,但不检查是否存在更多元素,这可能会导致发送多个上传的情况,并且额外的上传单通过未验证!

另见

Cookbook > Database Access & ORM > Saving Data > Modifying Request Data Before Building Entities

【讨论】:

非常感谢ndm花时间回复,我会试试你的解决方案。 确实,我知道我的验证是错误的,因为如果第一个文件的 mime 类型正常,我没有检查上传的第二个文件的 mime 类型。但是,检查文件是否为 FileUploadedInterface 的实例的目标是什么?我的意思是他们应该永远是,不是吗? @Oliv 他们应该是一个完美的世界,但是您无法控制客户端将发送到您的后端的内容,他们可以制作他们想要的任何有效负载。您可以使用表单保护器在一定程度上缓解它,但即使这样也无法抵御所有可能的攻击媒介,因此您必须始终假设您收到的数据可能是恶意的,而不是您期望的格式。在输入验证方面永远不要走简单的路线!

以上是关于当 cakephp 4 中没有文件上传时避免 mimeType 验证的主要内容,如果未能解决你的问题,请参考以下文章

CakePHP 上传文件的最佳实践

CakePHP 3 编辑上传

CakePHP 3.7 - 测试用例文件上传

“不能将 Laminas\Diactoros\UploadedFile 类型的对象用作数组”在 Cakephp 4 中具有多上传输入

列出 CakePHP 文件上传错误代码

CakePHP 3.7 - 测试用例文件上传