【问题标题】:如果 mime 类型检测失败,是否有一种安全可靠的方法来检查文件是否为文本文件?
【发布时间】:2017-09-08 02:00:26
【问题描述】:

我有一个网站可以处理用户上传的文本文件,为了确保它们实际上是文本文件,我检查 PHP 中的 mime 类型,如下所示:

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $filepath);
finfo_close($finfo);

这在大多数情况下都可以正常工作。问题是有时上传的文件包含一些控制字符(不可打印的字符,如 nul 或 stx)。尝试获取这些文件的 mime 类型总是返回 application/octet-stream。例如,我有一个 560 行长的文本文件,在第 12 行包含一个 nul 字符,因此被标识为 application/octet-stream

在检测mime类型不起作用时,有没有安全可靠的方法来检查上传的文件是否为文本文件?

【问题讨论】:

  • 定义“是文本文件”。您的意思是即使其中包含 NUL 字节,它也应该被识别为文本文件?那么什么符合文本文件的条件……?
  • 例如,如果是 pdf,它将以 %PDF- 开头,因此您可以阅读前四个字节
  • 如果我错了请纠正我,但据我所知,上传时的 mime 类型主要是一个线索(取决于我猜的文件扩展名),但不能保证内容符合声明。考虑到文本文件非常通用(二进制文件也可以作为文本读取),您可能需要在上传后检查内容
  • @Kaddath OP 使用 finfo“检查内容”。
  • @deceze 我猜当绝大多数是有效文本(超过 99%)时,我想将其作为文本文件处理,但我不确定这是否智能或安全去做

标签: php mime-types


【解决方案1】:

原来php中的大多数文件读取函数都是binary safe,这回答了我关于如何安全读取文件的问题。

我最终通过计算控制字符来解决我的问题。如果文件的一部分包含超过 1% 的控制字符,我认为它不是文本文件。

下面的函数适用于我使用它的目的(即使它只适用于 UTF-8 文件)

public static function isTextFile($filepath)
{
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mimeType = finfo_file($finfo, $filepath);
    finfo_close($finfo);

    if(substr($mimeType, 0, 5) === "text/") {
        return true;
    }

    if($mimeType !== "application/octet-stream") {
        return false;
    }

    $handle = fopen($filepath, 'rb');

    while (!feof($handle)) {
        $chunk = fread($handle, 4096);
        $controlCharCount = 0;

        if(($length = strlen($chunk)) === 0) {
            continue;
        }

        for($i = 0; $i < $length; $i++) {
            if($chunk[$i] !== "\r" && $chunk[$i] !== "\n" && ctype_cntrl($chunk[$i])) {
                $controlCharCount++;
            }
        }


        if(100 - $controlCharCount / $length * 100 < 99.0) {
            return false;
        }
    }

    fclose($handle);

    return true;
}

【讨论】:

    猜你喜欢
    • 2012-04-21
    • 2019-11-13
    • 1970-01-01
    • 2016-11-01
    • 2015-11-07
    • 2014-12-19
    • 1970-01-01
    • 2016-06-04
    • 2012-10-02
    相关资源
    最近更新 更多