为什么 Laravel 的 getMimeType() 方法会将一个类型属性为 "audio/mpeg" 的文件识别为 "application/octet-stream"?

40
我将尝试上传一个MP3文件到Laravel应用程序中,但遇到了一个问题,即使文件属性被设置为“audio/mpeg”,它仍然被上传为“application/octet-stream”(.bin)文件。当我尝试使用以下代码在服务器端进行测试时: ```php die(var_dump($file)); ```
dd($request->file('file'));

I get:

UploadedFile {#187 ▼
  -test: false
  -originalName: "CUS12309821-20-AUG-2016-13-48-13.mp3"
  -mimeType: "audio/mpeg"
  -size: 47000471
  -error: 0
  path: "/private/var/folders/c7/6ws0lxy95dd_lhz1k067_zkc0000gn/T"
  filename: "phpyZCsbU"
  basename: "phpyZCsbU"
  pathname: "/private/var/folders/c7/6ws0lxy95dd_lhz1k067_zkc0000gn/T/phpyZCsbU"
  extension: ""
  realPath: "/private/var/folders/c7/6ws0lxy95dd_lhz1k067_zkc0000gn/T/phpyZCsbU"
  aTime: 2016-09-20 12:56:00
  mTime: 2016-09-20 12:56:00
  cTime: 2016-09-20 12:56:00
  inode: 4565593
  size: 47000471
  perms: 0100600
  owner: 501
  group: 20
  type: "file"
  writable: true
  readable: true
  executable: false
  file: true
  dir: false
  link: false
}

看看当我使用这个方法时,它确实显示mimeType的文件属性是正确的"audio/mpeg"格式。然而,当我在上传后调用getMimeType()方法时,我会得到:

"application/octet-stream"

这是路由方法中的代码:

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{
    $file = $request->all();

    $filePath = Storage::putFile('file', $request->file('files'));

    dd($request->file('file')->getMimeType());

    $file['path'] = Storage::url($filePath);
    $file['size'] = Storage::size($filePath);
    $file['type'] = $request->file('file')->getMimeType();

    return $file;
}

这个问题似乎很独特,因为我正在使用 Laravel 框架,而其他遇到此问题的人则使用原始的 PHP。此外,其他人可能使用的 Excel 文件报告自己是 application/octet-stream 而不是 Excel 文件。最后,我认为这可能是 guess() 方法的问题,该方法由 getMethodType() 调用。有更多 Laravel 经验的人可能能够确认这一点。


我不同意。我正在使用Laravel框架,而他正在使用原始的PHP。此外,他的Excel文件可能已经报告自己为application/octet-stream而不是Excel文件。最后,我认为这可能是guess()方法的问题,该方法由getMethodType()调用。有更多Laravel经验的人可能可以确认这一点。 - Kirkland
我刚刚通过创建一个原始的PHP上传表单并上传文件,证明这是一个Laravel问题而不是PHP上传机制问题。var_dump($_FILES)的输出为:array(1) { ["fileToUpload"]=> array(5) { ["name"]=> string(15) "CUS12309821-20-AUG-2016-13-48-13.mp3" ["type"]=> string(10) "audio/mpeg" ["tmp_name"]=> string(66) "/private/var/folders/c7/6ws0lxy95dd_lhz1k067_zkc0000gn/T/phpf6cwMf" ["error"]=> int(0) ["size"]=> int(40340291) } } - Kirkland
@Kirkland:我有一个类似的问题,我的问题在这里:https://stackoverflow.com/questions/65868417/laravel-evaluates-jpeg-as-of-mime-type-application-octet-stream-but-php-and-ubun。你最终是如何解决你的问题的?当然,我可以使用php函数而不是Laravel验证器,但这一点都不优雅。我更希望看到Laravel做得对,没有错误。你觉得呢? - Steevie
3个回答

64

UploadedFile对象最终扩展自Symfony\Component\HttpFoundation\File\UploadedFile,该类从The type of the file as provided by PHP获取/设置mimeType。

要访问该mimeType,您需要调用$file->getClientMimeType()

但是,在Symfony函数的docblock中,它建议:

客户端mime类型是从上传文件的请求中提取的,因此不应将其视为安全值。

对于可信任的mime类型,请使用getMimeType() (基于文件内容猜测mime类型)。

在您的情况下,$file->getMimeType()应该是可信赖的,从内容中猜测mime类型,但它返回似乎无法确定mime类型的结果,即“application/octet-stream”。

附加信息

为帮助您决定。基本上,getClientMimeType()将返回由浏览器设置的mime类型。

调用getMimeType()会使用两种不同的技术来猜测mime类型:

  1. 使用二进制mime类型技术,查看以下命令的输出:file -b --mime %s 2>/dev/null(如果支持)。

  2. 第二种技术是使用php中是否存在的finfo_open命令。

如果您的系统同时存在1和2,从我看到的情况来看,2将优先考虑,而1将作为后备。

出于安全考虑,我个人更喜欢使用getMimeType()的结果。然而,另一个有趣的问题是:“浏览器mime类型检测有多可靠,使用了哪些技术” :-)

更新的示例

我为您提供一个示例。

对于我在“DropboxInstalled.dmg”上进行检查,以下是我的结果:

  1. 从命令行(终端)使用file -b --mime DropboxInstaller.dmg返回application/octet-stream

  2. 使用finfo_open功能

$finfo = new \finfo(FILEINFO_MIME_TYPE);
echo $finfo->file('./DropboxInstaller.dmg');

返回 application/x-iso9660-image


1
非常有趣!我仍在思考你提到的内容,我认为这最终将是答案。我想问一下你对使用getClientMimeType()方法所涉及的风险有何看法。只要文件扩展名和MIME类型匹配,并标记为非可执行文件,你认为存在哪些风险? - Kirkland
我宁愿在我的回答中添加内容,而不是留下未格式化的评论。 - Leon Vismer
如果这需要成为一个新问题,请告诉我。作为这两者之间的中间地带选择,您认为使用ID3标签怎么样? - Kirkland
如果这是一个独立的问题,你可能会得到更多的帮助。你没有提及你的平台,也没有说明你是否已经查看了你的平台默认使用哪种猜测方法。你可以查看vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php并添加一些调试信息。我认为猜测应该是准确的。不确定你的具体用例,但如果你想要双重检查一个mp3文件,如果猜测器返回文件的octet-stream并且它有ID3数据,那么它很有可能是一个mp3文件。 - Leon Vismer
对我来说,在MimeTypeGuesser实例的guess方法中抛出了FileNotFoundException异常,有什么想法吗? 另外,我理解你的帖子,但它对我仍然没有意义,决定MIME类型的问题应该由laravel来解决,而不是自动设置为octet-stream(例如通过在实例化UploadedFile对象时调用getMimeType()方法)。 - Fnr
我在上传Adobe Illustrator文件(.ai)时遇到了类似的问题。AI文件被识别为PDF,因此客户端MIME类型="application/postscript",但MIME类型返回"application/pdf"。不仅如此,使用Laravel的存储功能,它还将文件保存为.pdf而不是原始的.ai。目前我的唯一解决方案是覆盖扩展名。当它根据MIME类型自动更改文件扩展名时,这真的很头痛。 - Bill L.

11

我遇到了Laravel 5.4的问题。通过在php.ini中将post_max_sizeupload_max_filesize的值设置为更高的值来解决。

之后,实际上我不得不强制重启OSX才能使应用程序正确地反映出这些更改。


你肯定不需要进行“硬重启”,你只是没有重新启动Apache(或者你尝试重新启动但是重新启动了默认的)。 - Kyslik
@Kyslik 我不使用Apache,我使用Nginx。我重新启动了PHP服务和Nginx。但它仍未生效。因此我提到了显式硬重启。 - james2doyle
1
你是不是指的是php-fpm?我已经设置了很多服务器(并且我自己也在OS X上工作),从来没有听说过“需要硬重置”,因为服务不接受新配置。当然这很奇怪。现在一切都正常运行,所以这无关紧要。无论如何,你的“答案”并没有回答OP的问题。 - Kyslik

0
我在我的Laravel 9应用程序中使用application/octet-stream时遇到了这个错误,当我上传的文件太大时。
对我来说解决这个问题的方法是在.ini文件中设置这两个设置(增加限制)。
post_max_size = 128M
upload_max_filesize = 128M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接