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

Posted

技术标签:

【中文标题】ZipStream 在 PHP 中动态创建的 zip 文件不会在 OSX 中打开【英文标题】:Dynamically created zip files by ZipStream in PHP won't open in OSX 【发布时间】:2011-04-06 21:31:35 【问题描述】:

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

这很好用,除了 OS X 的默认解压缩程序存档实用程序之外,我尝试过的每个 zip 程序都可以打开生成的文件而没有错误。双击 .zip 文件,存档实用程序认为它看起来不是真正的 zip,而是压缩成 .cpgz 文件。

在 OS X 终端或 StuffIt Expander 中使用 unzip 或同上可以毫无问题地解压缩文件,但为了我们的用户,我需要默认程序(存档实用程序)来工作。

其他可接受的 zip 文件中的哪些内容(标志等)会使存档实用程序认为文件不是有效的 zip?

我读过this question,它似乎描述了一个类似的问题,但我没有设置任何通用位域位,所以这不是第三位问题,我很确定我有有效的 crc- 32 的,因为当我不这样做时,WinRAR 会出现故障。

如果有帮助,我很乐意发布一些代码或指向“坏”zip 文件的链接,但我几乎只是使用 ZipStream,强制它进入“大文件模式”并使用“存储”作为压缩方法。

编辑 - 我也尝试了“放气”压缩算法并得到相同的结果,所以我不认为它是“商店”。还值得指出的是,我每次从存储服务器中提取文件并在它们到达时将它们发送出去,因此要求在发送任何东西之前下载所有文件的解决方案将不可行(极端示例是 5GB+ 的 20MB 文件。用户不能等待所有 5GB 传输到压缩服务器才开始下载,否则他们会认为它已损坏)

这是一个 140 字节的“存储”压缩测试 zip 文件,它表现出这种行为:http://teknocowboys.com/test.zip

【问题讨论】:

【参考方案1】:

问题出在“需要提取的版本”字段中,我通过对由 ZipStream 创建的文件与由 Info-zip 创建的文件进行十六进制差异并通过差异,试图解决它们来发现。

ZipStream 默认设置为 0x0603。 Info-zip 将其设置为 0x000A。以前值的 Zip 文件似乎无法在存档实用程序中打开。也许它不支持该版本的功能?

将“需要提取的版本”强制设置为 0x000A 使生成的文件在存档实用程序中也像在其他任何地方一样打开。

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

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

如果您误报 Content-Length,IE 也需要同样的 hack,但不是下载不起作用的文件,而是无法完成下载并引发“下载中断”。

【讨论】:

另见,这个类似的问题:***.com/questions/1679986/… 救命稻草。我遇到了完全相同的问题(甚至使用 ZipStream)。 您好 ZorroDeLaArena,我很高兴看到您在堆栈溢出中提出的问题。基本上我正在尝试使用 ZipStream 库从 Amazon S3 动态下载和压缩大文件。但是到目前为止没有成功,如果您不介意可以分享您的代码块以供我参考以处理这种情况。 真的很难将 ZipStream 库与存储在 S3 上的文件一起使用,我认为您已经在使用该库来处理来自不同服务器的文件。我觉得您的代码块将帮助我处理我的情况,所以请您帮帮我。谢谢,湿婆... 我在 zipstream.php 文件中找到了 2 个匹配“需要提取的版本”的匹配项,那么您修改了哪个匹配项以及为该字段设置的新值是什么?【参考方案2】:

使用 ob_clean();flush();

例子:

    $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);
    

【讨论】:

【参考方案3】:

我遇到了这个确切的问题,但原因不同。

在我的情况下,php 生成的 zip 将从命令行打开,但不是通过 OSX 中的 finder。

在创建 zip 文件并将其作为响应发送回之前,我犯了一个错误,即允许一些 html 内容进入输出缓冲区。

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

// Output a zip file...

命令行解压缩程序显然可以容忍这一点,但 Mac 解压缩功能却不能。

【讨论】:

【参考方案4】:

不知道。如果外部 ZipString 类不起作用,请尝试其他选项。 PHP ZipArchive 扩展对您没有帮助,因为它不支持流式传输,但只能写入文件。

但您可以尝试使用标准的 Info-zip 实用程序。它可以像这样在 PHP 中调用:

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

这将导致未压缩的 zip 文件直接发送回客户端。

如果这没有帮助,那么 MacOS zip 前端可能不喜欢未压缩的东西。然后删除-0 标志。

【讨论】:

如果没有其他方法,passthru 是个好主意。谢谢!使用该库的优点是文件实际上托管在单独的服务器上,我在“压缩”之前暂时将它们拉下来。使用“zip”需要在发送任何数据之前拉下所有文件,给用户一个很长的问题,“这个网站坏了吗?”在任何明显的事情发生之前等待,而我自己创建 zip 让我可以在文件进入时传递它们,给人一种不断进步的感觉。 啊,好吧。那是一个很大的困难。它需要一个过于复杂的解决方法(nfs、sshfs 或 davfs)才能使用 zip 实用程序来完成这项工作。也许您应该尝试为该 zipstream 类启用压缩,至少用于测试。也许这足以改变 ZIP 格式以使 OSX 能够理解它。 我实际上已经用“deflate”算法尝试过它,我得到了相同的结果。 (我可能应该在原始问题中提到这一点。对不起。我会更新它)我认为这是 zip 标题中的一些问题,但我想我真的不知道。也许我可以用“zip”和 zipstream 制作同一个文件,然后做一个二进制差异来看看 zip 有什么不同 也许您可以在这里发布一个测试 zip(只是一个未压缩的 README.TEST),带有二进制/base64 转储。也许有人倾向于用十六进制编辑器戳它。【参考方案5】:

我在 Windows 和 Linux 上使用的 InfoZip 命令行工具都将版本 20 用于 zip 的“需要提取的版本”字段。这在 PHP 上也是需要的,因为默认压缩是 Deflate 算法。因此,“需要提取的版本”字段实际上应该是 0x0014。如果您将引用的 ZipStream 类中的“(6

作者基本上是在告诉您,该 zip 文件是在 OS/2 中使用 HPFS 文件系统创建的,并且所需的 Zip 版本早于 InfoZip 1.0。没有多少实现知道如何处理那个了;)

【讨论】:

【参考方案6】:

对于那些在 Symfony 中使用 ZipStream 的人,这是您的解决方案:https://***.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;

如果您的控制器操作响应不是 StreamedResponse,您可能会得到一个损坏的 zip,其中包含我发现的 html。

【讨论】:

【参考方案7】:

这是一个老问题,但我留下了对我有用的东西,以防万一它帮助别人。 设置选项时,您需要将零标头设置为 true 并将 zip 64 启用为 false(但这会将存档限制为 4 Gb):

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

Forer 描述的所有其他内容。 在https://github.com/maennchen/ZipStream-PHP/issues/71找到的解决方案

【讨论】:

以上是关于ZipStream 在 PHP 中动态创建的 zip 文件不会在 OSX 中打开的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Zipstream 从流中添加文件

[ZipStream] 使用 zipstream 下载多个 url

Laravel 5.4 中的 Zipstream - 数据已损坏

php 实现字符串最大子串长度

PHP 使用 GD 创建动态图像

pdb文件是啥?