通过ZipStream在PHP中动态创建zip文件;不能在OSX中打开

通过ZipStream在PHP中动态创建zip文件;不能在OSX中打开,php,macos,zip,Php,Macos,Zip,我有一个包含大量媒体文件的PHP站点,用户需要能够以.zip格式一次下载多个文件。我正在尝试使用“存储”压缩来提供即时拉链服务,这样我就不必在服务器上创建zip,因为有些文件很大,而且压缩速度非常慢 这非常有效,我尝试过的每个zip程序都可以打开生成的文件,除了OSX的默认解压程序Archive Utility之外,没有任何错误。双击.zip文件,存档实用程序确定它看起来不是真正的zip文件,而是压缩成.cpgz文件 在OSX终端或StuffIt Expander中使用解压或同上可以毫无问题地解

我有一个包含大量媒体文件的PHP站点,用户需要能够以.zip格式一次下载多个文件。我正在尝试使用“存储”压缩来提供即时拉链服务,这样我就不必在服务器上创建zip,因为有些文件很大,而且压缩速度非常慢

这非常有效,我尝试过的每个zip程序都可以打开生成的文件,除了OSX的默认解压程序Archive Utility之外,没有任何错误。双击.zip文件,存档实用程序确定它看起来不是真正的zip文件,而是压缩成.cpgz文件

在OSX终端或StuffIt Expander中使用解压或同上可以毫无问题地解压文件,但为了用户的利益,我需要默认程序(归档实用程序)

在其他可接受的zip文件中,什么样的东西(标志等)会使存档实用程序误认为文件不是有效的zip

我读过,它似乎描述了一个类似的问题,但我没有设置任何通用位域位,所以这不是第三位问题,我非常确定我有有效的crc-32,因为当我没有设置时,WinRAR会抛出一个错误

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

编辑-我也尝试过“deflate”压缩算法,得到了相同的结果,所以我认为这不是“store”。还值得指出的是,我正在从存储服务器上一次下载一个文件,并在文件到达时将其发送出去,因此要求在发送任何文件之前下载所有文件的解决方案将不可行(最极端的例子是20MB的5GB+文件。用户不能等到所有5GB传输到压缩服务器后再开始下载,否则他们会认为它坏了)


这是一个140字节的“存储”压缩测试zip文件,它显示了以下行为:

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

但您可以尝试使用标准Info zip实用程序。它可以从PHP中调用,如下所示:

#header("Content-Type: archive/zip");
passthru("zip -0 -q -r - *.*");
这将导致一个未压缩的zip文件直接发送回客户端

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

问题在于“需要解压缩的版本”字段,我通过对ZipStream创建的文件与InfoZip创建的文件进行十六进制差异,并分析差异,试图解决它们

ZipStream默认将其设置为0x0603。Info zip将其设置为0x000A。具有前一个值的zip文件似乎不会在存档实用程序中打开。可能它不支持该版本的功能

强制将“需要提取的版本”设置为0x000A会使生成的文件在Archive Utility中打开,就像在其他地方一样

编辑:此问题的另一个原因是zip文件是使用Safari(用户代理版本>=537)下载的,并且您在发送内容长度标题时低估了文件大小

我们采用的解决方案是检测Safari>=537服务器端,如果您使用的是Safari,我们将确定内容长度大小和实际大小之间的差异(这取决于您的具体应用程序),并在调用$zipStream->finish()后,回显chr(0)以达到正确的长度。生成的文件在技术上是错误的,您在zip中的任何注释都不会显示,但所有zip程序都可以打开它并提取文件


如果你误报了你的内容长度,IE也需要同样的技巧,但不是下载一个不起作用的文件,它只是不会完成下载并抛出一个“下载中断”。

我在Windows和Linux上使用的InfoZip命令行工具使用版本20作为zip的“需要解压缩的版本”字段。这在PHP上也是必需的,因为默认压缩是Deflate算法。因此“需要提取的版本”字段实际上应该是0x0014。如果更改“(6使用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);
    }

我确实遇到过这个问题,但原因不同

在我的例子中,php生成的zip将从命令行打开,但不会通过OSX中的finder打开

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

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

// Output a zip file...


对于在Symfony中使用ZipStream的用户,以下是您的解决方案:


如果您的控制器操作响应不是StreamdResponse,您可能会得到一个损坏的包含html的zip文件,正如我发现的那样。

这是一个老问题,但我留下了它对我有用的内容,以防它对其他人有所帮助。 设置选项时,需要将Zero header设置为true,并将enable zip 64设置为false(这会将存档限制为4 Gb):

其他一切如所述。

passthru上找到的解决方案是一个好主意,如果没有其他办法。谢谢!使用该库的好处是文件实际上托管在一个单独的服务器上,我在“压缩”之前暂时将它们拉下来。使用“压缩”要求在发送任何数据之前将所有文件拉下来,给用户一个很长的时间,“这个网站坏了吗?”?“在任何明显的事情发生之前等待,而我自己创建的zip让我能够在文件传入时传递它们,让它们看起来不断进步。啊,好吧。那是一个很大的困难。这需要一个过于复杂的解决方法(nfs、sshfs或davfs)也许你应该试着为zipstream类启用压缩,至少是为了测试。
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;
}
$options->setZeroHeader(true);
$opt->setEnableZip64(false)