PHP 中的 ZipStream 动态创建的 zip 文件无法在 OSX 中打开
我有一个包含大量媒体文件的 PHP 网站,用户需要能够一次下载多个文件作为 .zip。我正在尝试使用 ZipStream 通过“存储”压缩动态提供 zip所以我实际上不必在服务器上创建 zip,因为有些文件很大,并且压缩所有文件的速度非常慢。
这效果很好,除了 OS X 的默认解压缩程序 Archive Utility 之外,我尝试过的每个 zip 程序都可以打开生成的文件,没有任何错误。您双击 .zip 文件,Archive Utility 会认为它看起来不是真正的 zip,而是压缩为 .cpgz 文件。
在 OS X 终端或 StuffIt Expander 中使用 unzip 或同上可以毫无问题地解压缩文件,但为了我们的用户,我需要默认程序(存档实用程序)来工作。
可接受的 zip 文件中的哪些内容(标志等)可能会使 Archive Utility 认为该文件不是有效的 zip?
我已阅读这个问题,这似乎描述了一个类似的问题,但我没有设置任何通用位字段位,因此这不是第三位问题,而且我很确定我有有效的 crc-32,因为当我没有时, WinRAR 发脾气了。
如果有帮助的话,我很乐意发布一些代码或指向“坏”zip 文件的链接,但我几乎只是使用 ZipStream,强制其进入“大文件模式”并使用“存储”作为压缩方法。
编辑 - 我也尝试过“放气”压缩算法并得到相同的结果,所以我不认为这是“存储”。还值得指出的是,我一次从存储服务器中提取一个文件,并在文件到达时将其发送出去,因此要求在发送任何内容之前下载所有文件的解决方案是不可行的(极端情况)例如,超过 5GB 的 20MB 文件。用户不能等待所有 5GB 在下载开始之前传输到压缩服务器,否则他们会认为它已损坏)
这是一个 140 字节的“存储”压缩测试 zip 文件,它显示了此行为: http://teknocowboys.com/test.zip
I have a PHP site with a lot of media files and users need to be able to download multiple files at a time as a .zip. I'm trying to use ZipStream to serve the zips on the fly with "store" compression so I don't actually have to create a zip on the server, since some of the files are huge and it's prohibitively slow to compress them all.
This works great and the resulting files can be opened by every zip program I've tried with no errors except for OS X's default unzipping program, Archive Utility. You double click the .zip file and Archive Utility decides it doesn't look a real zip and instead compresses into a .cpgz file.
Using unzip or ditto in the OS X terminal or StuffIt Expander unzips the file with no problem but I need the default program (Archive Utility) to work for the sake of our users.
What sort of things (flags, etc.) in otherwise acceptable zip files can trip Archive Utility into thinking a file isn't a valid zip?
I've read this question, which seems to describe a similar issue but I don't have any of the general purpose bitfield bits set so it's not the third bit issue and I'm pretty sure I have valid crc-32's because when I don't, WinRAR throws a fit.
I'm happy to post some code or a link to a "bad" zip file if it would help but I'm pretty much just using ZipStream, forcing it into "large file mode" and using "store" as the compression method.
Edit - I've tried the "deflate" compression algorithm as well and get the same results so I don't think it's the "store". It's also worth pointing out that I'm pulling down the files one a time from a storage server and sending them out as they arrive so a solution that requires all the files to be downloaded before sending anything isn't going to be viable (extreme example is 5GB+ of 20MB files. User can't wait for all 5GB to transfer to zipping server before their download starts or they'll think it's broken)
Here's a 140 byte, "store" compressed, test zip file that exhibits this behavior: http://teknocowboys.com/test.zip
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
问题出在“需要提取的版本”字段中,我通过对 ZipStream 创建的文件与 Info-zip 创建的文件进行十六进制比较并检查差异并尝试解决它们来发现该字段。
ZipStream 默认将其设置为 0x0603。 Info-zip 将其设置为 0x000A。具有先前值的 Zip 文件似乎无法在存档实用程序中打开。也许它不支持该版本的功能?
将“需要提取的版本”强制为 0x000A 可以使生成的文件在 Archive Utility 中打开,就像在其他地方一样。
编辑:此问题的另一个原因是,如果 zip 文件是使用 Safari(用户代理版本 >= 537)下载的,并且您在发送 Content-Length 标头时低估了文件大小。
我们采用的解决方案是检测 Safari >= 537 服务器端,如果您正在使用该服务器端,我们将确定 Content-Length 大小与实际大小之间的差异(如何执行此操作取决于您的具体应用程序),然后调用 $zipStream->finish(),我们回显 chr(0) 以达到正确的长度。生成的文件技术上格式错误,您在 zip 中添加的任何注释都不会显示,但所有 zip 程序都可以打开它并提取文件。
如果您误报了内容长度,IE 也需要同样的技巧,但它不会下载不起作用的文件,而是无法完成下载并抛出“下载中断”。
The problem was in the "version needed to extract" field, which I found by doing a hex diff on a file created by ZipStream vs a file created by Info-zip and going through the differences, trying to resolve them.
ZipStream by default sets it to 0x0603. Info-zip sets it to 0x000A. Zip files with the former value don't seem to open in Archive Utility. Perhaps it doesn't support the features at that version?
Forcing the "version needed to extract" to 0x000A made the generated files open as well in Archive Utility as they do everywhere else.
Edit: Another cause of this issue is if the zip file was downloaded using Safari (user agent version >= 537) and you under-reported the file size when you sent out your Content-Length header.
The solution we employ is to detect Safari >= 537 server side and if that's what you're using, we determine the difference between the Content-Length size and the actual size (how you do this depends on your specific application) and after calling $zipStream->finish(), we echo chr(0) to reach the correct length. The resulting file is technically malformed and any comment you put in the zip won't be displayed, but all zip programs will be able to open it and extract the files.
IE requires the same hack if you're misreporting your Content-Length but instead of downloading a file that doesn't work, it just won't finish downloading and throws a "download interrupted".
使用 ob_clean(); 和 flush();
示例:
use ob_clean(); and flush();
Example :
我也遇到过这个问题,但原因不同。
在我的例子中,php 生成的 zip 可以从命令行打开,但不能通过 OSX 中的 finder 打开。
我犯了一个错误,在创建 zip 文件并将其作为响应发送回来之前,允许一些 HTML 内容进入输出缓冲区。
命令行解压缩程序显然可以容忍这种情况,但 Mac 解档功能却不能。
I've had this exact issue but with a different cause.
In my case the php generated zip would open from the command line, but not via finder in OSX.
I had made the mistake of allowing some HTML content into the output buffer prior to creating the zip file and sending that back as the response.
The command line unzip program was evidently tolerant of this but the Mac unarchive function was not.
不知道。如果外部 ZipString 类不起作用,请尝试其他选项。 PHP
ZipArchive
扩展不会帮助您,因为它不支持流式传输,但只写入文件。但您可以尝试标准的 Info-zip 实用程序。它可以从 PHP 内部调用,如下所示:
这将导致未压缩的 zip 文件直接发送回客户端。
如果这没有帮助,那么 MacOS zip 前端可能不喜欢未压缩的内容。然后删除
-0
标志。No idea. If the external ZipString class doesn't work, try another option. The PHP
ZipArchive
extension won't help you, since it doesn't support streaming but only ever writes to files.But you could try the standard Info-zip utility. It can be invoked from within PHP like this:
That would lead to an uncompressed zip file directly send back to the client.
If that doesn't help, then the MacOS zip frontend probably doesn't like uncompressed stuff. Remove the
-0
flag then.我在 Windows 和 Linux 上使用的 InfoZip 命令行工具都使用版本 20 作为 zip 的“解压所需版本”字段。 PHP 也需要这样做,因为默认压缩是 Deflate 算法。因此,“提取所需的版本”字段实际上应该是 0x0014。如果将引用的 ZipStream 类中的“(6 << 8) +3”代码更改为“20”,则应该获得跨平台的有效 Zip 文件。
作者基本上是在告诉您该 zip 文件是使用 HPFS 文件系统在 OS/2 中创建的,并且所需的 Zip 版本早于 InfoZip 1.0。没有多少实现知道如何处理该问题了;)
The InfoZip commandline tool I'm using, both on Windows and Linux, uses version 20 for the zip's "version needed to extract" field. This is needed on PHP as well, as the default compression is the Deflate algorithm. Thus the "version needed to extract" field should really be 0x0014. If you alter the "(6 << 8) +3" code in the referenced ZipStream class to just "20", you should get a valid Zip file across platforms.
The author is basically telling you that the zip file was created in OS/2 using the HPFS file system, and the Zip version needed predates InfoZip 1.0. Not many implementations know what to do about that one any longer ;)
对于在 Symfony 中使用 ZipStream 的用户,以下是您的解决方案:https://stackoverflow.com/a/44706446/136151
控制器操作响应不是 StreamedResponse,正如我发现的那样,您可能会收到包含 html 的损坏的 zip。
For those using ZipStream in Symfony, here's your solution: https://stackoverflow.com/a/44706446/136151
If your controller action response is not a StreamedResponse, you are likely going to get a corrupted zip containing html as I found out.
这是一个老问题,但我留下了它对我有用的东西,以防它对其他人有帮助。
设置选项时,您需要将零标头设置为 true 并将 zip 64 启用为 false(但这会将存档限制为 4 Gb):
其他内容如 前任。
在 https://github.com/maennchen/ZipStream-PHP/issues/71 上找到的解决方案
It's an old question but I leave what it worked for me just in case it helps someone else.
When setting the options you need set Zero header to true and enable zip 64 to false (this will limit the archive to archive to 4 Gb though):
Everything else as described by Forer.
Solution found on https://github.com/maennchen/ZipStream-PHP/issues/71