简体   繁体   English

ZipStream 在 PHP 中动态创建的 zip 文件不会在 OSX 中打开

[英]Dynamically created zip files by ZipStream in PHP won't open in OSX

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.我有一个包含大量媒体文件的 PHP 站点,用户需要能够一次下载多个 .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.我正在尝试使用ZipStream通过“存储”压缩来动态提供 zip,因此我实际上不必在服务器上创建 zip,因为某些文件很大,而且压缩它们的速度非常慢。

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.这很好用,并且我尝试过的每个 zip 程序都可以打开生成的文件,没有错误,除了 OS X 的默认解压缩程序 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.您双击 .zip 文件,Archive Utility 认为它看起来不是真正的 zip,而是压缩为 .cpgz 文件。

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.在 OS X 终端或 StuffIt Expander 中使用 unzip 或 ditto 可以毫无问题地解压缩文件,但为了我们的用户,我需要默认程序(Archive Utility)才能工作。

What sort of things (flags, etc.) in otherwise acceptable zip files can trip Archive Utility into thinking a file isn't a valid zip?其他可接受的 zip 文件中的哪些类型的东西(标志等)会使 Archive Utility 认为文件不是有效的 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.我读过这个问题,它似乎描述了一个类似的问题,但我没有设置任何通用位域位,所以它不是第三位问题,我很确定我有有效的 crc-32,因为当我不要,WinRAR 会引起不适。

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.如果有帮助,我很高兴发布一些代码或指向“坏”zip 文件的链接,但我几乎只是使用 ZipStream,将其强制为“大文件模式”并使用“存储”作为压缩方法。

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)还值得指出的是,我一次从存储服务器中提取一个文件并在它们到达时将它们发送出去,因此要求在发送任何文件之前下载所有文件的解决方案将不可行(极端示例是 5GB+ 的 20MB 文件。用户不能等待所有 5GB 在下载开始之前传输到压缩服务器,否则他们会认为它已损坏)

Here's a 140 byte, "store" compressed, test zip file that exhibits this behavior: http://teknocowboys.com/test.zip这是一个 140 字节的“存储”压缩测试 zip 文件,显示了这种行为: http : //teknocowboys.com/test.zip

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 创建的文件与由 Info-zip 创建的文件进行十六进制差异并检查差异并尝试解决它们来发现该字段。

ZipStream by default sets it to 0x0603. ZipStream 默认将其设置为 0x0603。 Info-zip sets it to 0x000A. Info-zip 将其设置为 0x000A。 Zip files with the former value don't seem to open in Archive Utility.具有前一个值的 Zip 文件似乎无法在 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.将“需要提取的版本”强制为 0x000A 会使生成的文件在存档实用程序中打开,就像在其他任何地方一样。

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.编辑:此问题的另一个原因是,如果 zip 文件是使用 Safari(用户代理版本 >= 537)下载的,并且您在发送 Content-Length 标头时少报了文件大小。

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.我们采用的解决方案是检测 Safari >= 537 服务器端,如果这是您使用的,我们会确定 Content-Length 大小与实际大小之间的差异(您如何执行此操作取决于您的特定应用程序)并在调用之后$zipStream->finish(),我们回显 chr(0) 以达到正确的长度。 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.生成的文件在技术上是格式错误的,您在 zip 中放置的任何注释都不会显示,但所有 zip 程序都可以打开它并提取文件。

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".如果您误报了 Content-Length,IE 需要同样的 hack 但不是下载不起作用的文件,它只是无法完成下载并引发“下载中断”。

use ob_clean();使用ob_clean(); and flush();冲洗();

Example :例子 :

    $file =  __UPLOAD_PATH . $projectname . '/' . $fileName;

    $zipname = "watherver.zip"
    $zip = new ZipArchive(); 
    $zip_full_path_name = __UPLOAD_PATH . $projectname . '/' . $zipname;
    $zip->open($zip_full_path_name, ZIPARCHIVE::CREATE);
    $zip->addFile($file); // Adding one file for testing
    $zip->close();

    if(file_exists($zip_full_path_name)){
        header('Content-type: application/zip');
        header('Content-Disposition: attachment; filename="'.$zipname.'"');
        ob_clean();
        flush();
        readfile($zip_full_path_name);
        unlink($zip_full_path_name);
    }

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.在我的情况下,php 生成的 zip 将从命令行打开,但不能通过 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.我犯了一个错误,在创建 zip 文件并将其作为响应发回之前,允许一些 HTML 内容进入输出缓冲区。

<some html></....>
<?php

// Output a zip file...

The command line unzip program was evidently tolerant of this but the Mac unarchive function was not.命令行解压缩程序显然可以容忍这一点,但 Mac 解压缩功能则不然。

For those using ZipStream in Symfony, here's your solution: https://stackoverflow.com/a/44706446/136151对于那些在 Symfony 中使用 ZipStream 的人,这是您的解决方案: https ://stackoverflow.com/a/44706446/136151

use Symfony\Component\HttpFoundation\StreamedResponse;
use Aws\S3\S3Client;    
use ZipStream;

//...

/**
 * @Route("/zipstream", name="zipstream")
 */
public function zipStreamAction()
{
    //test file on s3
    $s3keys = array(
      "ziptestfolder/file1.txt"
    );

    $s3Client = $this->get('app.amazon.s3'); //s3client service
    $s3Client->registerStreamWrapper(); //required

    $response = new StreamedResponse(function() use($s3keys, $s3Client) 
    {

        // Define suitable options for ZipStream Archive.
        $opt = array(
                'comment' => 'test zip file.',
                'content_type' => 'application/octet-stream'
              );
        //initialise zipstream with output zip filename and options.
        $zip = new ZipStream\ZipStream('test.zip', $opt);

        //loop keys useful for multiple files
        foreach ($s3keys as $key) {
            // Get the file name in S3 key so we can save it to the zip 
            //file using the same name.
            $fileName = basename($key);

            //concatenate s3path.
            $bucket = 'bucketname';
            $s3path = "s3://" . $bucket . "/" . $key;        

            //addFileFromStream
            if ($streamRead = fopen($s3path, 'r')) {
              $zip->addFileFromStream($fileName, $streamRead);        
            } else {
              die('Could not open stream for reading');
            }
        }

        $zip->finish();

    });

    return $response;
}

If your controller action response is not a StreamedResponse, you are likely going to get a corrupted zip containing html as I found out.如果您的控制器操作响应不是 StreamedResponse,您可能会得到一个包含 html 的损坏 zip,正如我发现的那样。

No idea.不知道。 If the external ZipString class doesn't work, try another option.如果外部 ZipString 类不起作用,请尝试其他选项。 The PHP ZipArchive extension won't help you, since it doesn't support streaming but only ever writes to files. PHP ZipArchive扩展不会帮助你,因为它不支持流,但只写入文件。

But you could try the standard Info-zip utility.但是您可以尝试使用标准的 Info-zip 实用程序。 It can be invoked from within PHP like this:它可以像这样从 PHP 内部调用:

#header("Content-Type: archive/zip");
passthru("zip -0 -q -r - *.*");

That would lead to an uncompressed zip file directly send back to the client.这将导致未压缩的 zip 文件直接发送回客户端。

If that doesn't help, then the MacOS zip frontend probably doesn't like uncompressed stuff.如果这没有帮助,那么 MacOS zip 前端可能不喜欢未压缩的东西。 Remove the -0 flag then.然后删除-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.我在 Windows 和 Linux 上使用的 InfoZip 命令行工具都使用版本 20 作为 zip 的“需要提取的版本”字段。 This is needed on PHP as well, as the default compression is the Deflate algorithm.这在 PHP 上也是需要的,因为默认压缩是 Deflate 算法。 Thus the "version needed to extract" field should really be 0x0014.因此,“需要提取的版本”字段实际上应该是 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.如果您将引用的 ZipStream 类中的“(6 << 8) +3”代码更改为“20”,您应该会获得跨平台的有效 Zip 文件。

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.作者基本上是在告诉您 zip 文件是在 OS/2 中使用 HPFS 文件系统创建的,并且所需的 Zip 版本早于 InfoZip 1.0。 Not many implementations know what to do about that one any longer ;)没有多少实现知道如何处理那个了 ;)

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):设置选项时,您需要将 Zero header 设置为 true 并将 zip 64 设置为 false(这会将存档限制为 4 Gb):

$options->setZeroHeader(true);
$opt->setEnableZip64(false)

Everything else as described by Forer .其他一切如Forer 所述 Solution found on https://github.com/maennchen/ZipStream-PHP/issues/71https://github.com/maennchen/ZipStream-PHP/issues/71 上找到的解决方案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM