如果 mime 类型检测失败,是否有安全可靠的方法来检查文件是否为文本文件?

Is there a safe and reliable way to check if a file is a text file if mime type detecting fails?

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

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

大多数时候效果很好。问题是有时上传的文件包含一些控制字符(不可打印的字符,如 nul 或 stx)。始终尝试获取这些文件的 MIME 类型 returns application/octet-stream。例如,我有一个 560 行长的文本文件,第 12 行包含一个空字符,因此被识别为 application/octet-stream

在检测mime类型无效的情况下,有什么安全可靠的方法可以检测上传的文件是否为文本文件?

原来 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;
}