Laravel:表单文件上传失败-检测到错误的类型

时间:2019-08-01 03:20:18

标签: php html laravel file safari

我最近启用了上传简历的系统,但拒绝率却异常高,大约为15%。当我查看日志时,它显示了以下结果:

  • 客户端报告的扩展名为docx(基于$file->getClientOriginalExtension()
  • 客户端报告的MIME类型为application/vnd.openxmlformats-officedocument.wordprocessingml.document(基于$file->getClientMimeType()
  • Laravel / PHP检测到文件为.bin(基于$file->extension()

换句话说,客户端说它正在上传docx文件,但是服务器说它接收到一个bin。起初我以为这只是腐败,但它经常发生。

到目前为止,我仅在日志中捕获了一些日志,但是我注意到每次Safari浏览器都是浏览器。 Safari是否存在系统性问题?如果是这样,我该如何解决?

还有什么可能导致此问题?

我正在用Vue触发表单提交,但是我看不出这有什么用。这是Vue代码:

document.getElementById("new-application").submit();

3 个答案:

答案 0 :(得分:5)

问题是PHP使用OS(在本例中为Ubuntu)中的file命令来根据文件内容猜测扩展名。错了。升级Ubuntu可能会有所帮助。

答案 1 :(得分:2)

Safari以其mime类型而闻名,这是不正确的。我确定在您的情况下,它不会发送应有的docx哑剧,而是说它是octet-stream。好吧,您唯一的解决方法是(如果您的文件未损坏)手动设置文件扩展名以更正一个扩展名或从浏览器手动发送文件,确保MIME正确。

答案 2 :(得分:2)

docx是Microsoft Word Open XML格式文档,基于XML,所有内容都存储为单独的文件,最终压缩为一个ZIP压缩文件。好像是一个文件容器。

我们不应该将文件扩展名与mimetype一样。据了解,扩展名为docx的普通文件(带有docx的mp4文件不应视为普通文件)可以具有这些模仿类型之一。

'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
'application/zip'
'application/CDFV2'

在您的代码中

$file->getClientOriginalExtension()从上传的原始文件名中提取扩展名,不应将其视为安全值。

$file->getClientMimeType()从客户端请求中提取哑剧类型,不应将其视为安全值。

这两个函数都在./vendor/symfony/http-foundation/File/UploadedFile.php中实现

然后看一下代码中的最后一个函数

$file->extension()使用某种方法来猜测(可能不准确) 。根据文件内容,系统依次为php的finfomanualfile。如下面的源代码所示,因此并非总是准确的。请参阅file command apparently returning wrong MIME type

在这里您应该得到它 。如果要进一步操作,请参见下面的函数extension的源代码。


源代码
extension调用guessExtension来猜测文件扩展名,并且guessExtension使用guessMiMeType返回的模仿类型

// FileHelpers.php
public function extension()
{
    return $this->guessExtension();
}
// File.php
public function guessExtension()
{
    return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null;
}
...
public function getMimeType()
{
    return MimeTypes::getDefault()->guessMimeType($this->getPathname());
}

guessMiMeType使用两个猜测器来猜测模仿类型。 php的finfo和系统的file,以及finfo的优先级比file高。

//MimeTypes.php
public function __construct(array $map = [])
{
    foreach ($map as $mimeType => $extensions) {
        $this->extensions[$mimeType] = $extensions;

        foreach ($extensions as $extension) {
            $this->mimeTypes[$extension] = $mimeType;
        }
    }
    $this->registerGuesser(new FileBinaryMimeTypeGuesser());
    $this->registerGuesser(new FileinfoMimeTypeGuesser());
}
...
/**
* Registers a MIME type guesser.
*
* The last registered guesser has precedence over the other ones.
*/
public function registerGuesser(MimeTypeGuesserInterface $guesser)
{
    array_unshift($this->guessers, $guesser);
}
...
public function guessMimeType(string $path): ?string
{
    foreach ($this->guessers as $guesser) {
        if (!$guesser->isGuesserSupported()) {
            continue;
        }

        if (null !== $mimeType = $guesser->guessMimeType($path)) {
            return $mimeType;
        }
    }

    if (!$this->isGuesserSupported()) {
        throw new LogicException('Unable to guess the MIME type as no guessers are available (have you enable the php_fileinfo extension?).');
    }

    return null;
}
//FileinfoMimeTypeGuesser.php
public function guessMimeType(string $path): ?string
{
    if (!is_file($path) || !is_readable($path)) {
        throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path));
    }

    if (!$this->isGuesserSupported()) {
        throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__));
    }

    if (false === $finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
        return null;
    }

    return $finfo->file($path);
}
//FileBianryMimeTypeGuesser.php
public function __construct(string $cmd = 'file -b --mime %s 2>/dev/null')
{
    $this->cmd = $cmd;
}
    public function guessMimeType(string $path): ?string
{
    if (!is_file($path) || !is_readable($path)) {
        throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path));
    }

    if (!$this->isGuesserSupported()) {
        throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__));
    }

    ob_start();

    // need to use --mime instead of -i. see #6641
    passthru(sprintf($this->cmd, escapeshellarg($path)), $return);
    if ($return > 0) {
        ob_end_clean();

        return null;
    }

    $type = trim(ob_get_clean());

    if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
        // it's not a type, but an error message
        return null;
    }

    return $match[1];
}