PHP在Windows中的ZipArchive损坏问题

26

我正在使用PHP的ZipArchive类创建一个包含照片的zip文件,并将其提供给浏览器下载。以下是我的代码:

/**
 * Grabs the order, packages the files, and serves them up for download.
 *
 * @param string $intEntryID 
 * @return void
 * @author Jesse Bunch
 */
public static function download_order_by_entry_id($intUniqueID) {

    $objCustomer = PhotoCustomer::get_customer_by_unique_id($intUniqueID);

    if ($objCustomer):

        if (!class_exists('ZipArchive')):
            trigger_error('ZipArchive Class does not exist', E_USER_ERROR);
        endif;

        $objZip = new ZipArchive();
        $strZipFilename = sprintf('%s/application/tmp/%s-%s.zip', $_SERVER['DOCUMENT_ROOT'], $objCustomer->getEntryID(), time());

        if ($objZip->open($strZipFilename, ZIPARCHIVE::CREATE) !== TRUE):

            trigger_error('Unable to create zip archive', E_USER_ERROR);

        endif;          

        foreach($objCustomer->arrPhotosRequested as $objPhoto):

            $filename = PhotoCart::replace_ee_file_dir_in_string($objPhoto->strHighRes);
            $objZip->addFile($filename,sprintf('/press_photos/%s-%s', $objPhoto->getEntryID(), basename($filename)));

        endforeach;

        $objZip->close();

        header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($strZipFilename)).' GMT',  TRUE, 200);
        header('Cache-Control: no-cache', TRUE);
        header('Pragma: Public', TRUE);
        header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT', TRUE);
        header('Content-Length: '.filesize($strZipFilename), TRUE);
        header('Content-disposition: attachment; filename=press_photos.zip', TRUE);

        header('Content-Type: application/octet-stream', TRUE);

        ob_start();
        readfile($strZipFilename);
        ob_end_flush();
        exit;

    else:

        trigger_error('Invalid Customer', E_USER_ERROR);

    endif;

}

这段代码在所有浏览器中都能很好地工作,但在IE中存在问题。在IE中,文件可以正确下载,但zip归档文件为空。尝试提取文件时,Windows告诉我zip归档文件已损坏。有人遇到过这个问题吗?

编辑 更新:根据@profitphp的建议,我将标头更改为以下内容:

header("Cache-Control: public");
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
//header("Content-Description: File Transfer");
//header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=\"pressphotos.zip\"");
//header("Content-Transfer-Encoding: binary");
header("Content-length: " . filesize($strZipFilename));

此外,这是在Windows上使用Firefox打开时出现的错误截图:

alt text

这个错误在Windows上的IE和Firefox中都会出现。在Mac上运行正常。而且,在Windows上,文件大小似乎是正确的:

alt text

编辑#2 这个问题已经解决。请看下面我的回答。


我遇到了同样的问题,这个答案对我有帮助:PHP发送的ZIP存档文件已损坏 - MaRmAR
14个回答

32

我遇到了同样的问题,我的解决方案与这个帖子上正确答案类似。当你将文件放入存档时,不能有绝对路径(以斜杠开头的文件),否则它在Windows中无法打开。

所以他让它工作的原因不是因为他删除了包含文件夹,而是因为他删除了开头的斜杠。

我通过更改

$zip->addFile($file, $file); // $file is something like /path/to/file.png

// we make file relative by removing beginning slash so it will open in Windows
$zip->addFile($file, ltrim($file, '/'));

然后它就能在Windows中打开了!

这很可能是pclzip(Plahcinski的回答)有效的原因。我敢打赌它会自动去掉开头的斜杠。

如果没有PHP ZipArchive::addFile文档页面上的特定评论,我就想不出来了。


2
这对我来说是个问题,帮了很多忙。 - Jake N
谢谢,对我有用。看起来是一个常见的问题,奇怪他们在PHP文档中没有提到它。 - sopryshko

14

我最近遇到了你所描述的类似问题。我发现ZipArchive不太稳定。

我使用了这个简单的库解决了我的问题:

http://www.phpconcept.net/pclzip

include_once('libs/pclzip.lib.php');

...

function zip($source, $destination){
$zipfile = new PclZip($destination);
$v_list = $zipfile->create($source, '', $source); }

$source = 我想要压缩的文件夹 $destination = 压缩文件的位置

我花了两天时间研究ZipArchive,但是在5分钟内使用PCLZip解决了所有问题。

希望这可以帮助你和其他遇到这个问题的人(因为这是谷歌搜索结果中排名靠前的一个)。


在Windows环境下。不幸的是,在我的网页上,它并没有很好地工作:它只压缩了一个文件而不是多个文件。 - Dev.Jaap
在Windows上对我有用。像你说的那样,我花了将近一个小时查看ZipArchive,但这个方法只用了2分钟就解决了问题。谢谢! - Flion
很棒的库。要在PHP7.1服务器上使用它,只需将方法“PclZip”更改为“__construct”,并在需要整数的地方添加“(int)”。 - Phoca
库的链接已失效。 - Ravi Mattar

14

所有这些建议可能会对您有所帮助,但在我的情况下,我需要在第一个header('')之前写入ob_clean();,因为我包含的某些文件在打印之前会输出一些字符,这些字符会破坏Windows上的压缩文件。

$zip=new ZipArchive();
$zip->open($filename, ZIPARCHIVE::CREATE);
$zip->addFile($file_to_attach,$real_file_name_to_attach);
$zip->close();

ob_clean();
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename="file.zip"');
readfile($filename);
exit;

1
这对我有用,尝试了其他所有方法都没有成功。 - llanato
这个对我也起作用了。我也尝试了其他所有方法。这应该是被接受的答案。注意:我只需要添加一行代码:ob_clean(),就可以让我的代码工作了。花了一两个小时试图获取可以打开的zip文件。 - mikekehrli

6

好的,在经历了许多困难后,我终于找出了问题所在。问题出现在以下代码行:

$objZip->addFile($filename,sprintf('/press_photos/%s-%s', $objPhoto->getEntryID(), basename($filename)));

由于某种原因,在zip文件中,本地(内部)文件名路径中的/press_photos/部分导致Windows认为该zip文件已经损坏。将该行修改如下后,Windows正确打开了zip文件。太好了。
$objZip->addFile($filename,sprintf('%s-%s', $objPhoto->getEntryID(), basename($filename)));

3
我成功地将文件添加到归档文件后更名。如果您不需要在归档文件中维护目录结构,则这是一个很好的解决方案: $zip->addFile($uri, basename($uri) ); $zip->renameName(basename($uri), str_replace('_', '-', basename($uri))); - doub1ejack

5

我有一个文件名很长,包含空格、破折号和日期字符的压缩文件。结果一旦我将文件名改成简单的名称,例如export.zip,它就可以正常工作了。 - Nathan Hangen

3

我以前遇到过这个问题。尝试去掉内容类型头部。下面是我为此编写的代码,在IE和FF中均有效。请注意,有些行被注释掉了,因为在不同情况下可能会出现相同的问题。

header("Cache-Control: public");
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
//header("Content-Description: File Transfer");
//header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=\"adwords-csv.zip\"");
//header("Content-Transfer-Encoding: binary");
header("Content-length: " . filesize($filename)); 

我更改了头文件,但是在Windows上压缩文件仍然显示为“损坏”,但在我的Mac上却正常。 - Jesse Bunch
尝试让标题看起来与我发布的完全一样,包括顺序等。在使用zip文件中的某些文件名时,我也遇到了类似的问题。请使用非常基本的字符集。现在你提到了Windows。如果你在Windows上使用Firefox、Chrome等浏览器,是否会发生同样的事情?这是浏览器问题还是zip阅读器问题? - profitphp
感谢您的时间,我已经更新了这个问题并提供了更多信息。 - Jesse Bunch
好的,这可能不是头文件的问题,看起来更像是与Windows默认的zip阅读器有关。你尝试过使用WinRAR解压缩吗?在我的实现中,我只是使用了命令行中的zip而不是zip存档类。如果最坏的情况发生,你可能想尝试一下这个。 - profitphp
你说得对,当我将那个“损坏”的zip文件从Windows复制到我的Mac上时,它可以顺利打开。 - Jesse Bunch
谢谢你的帮助,事实证明这根本不是头文件的问题。实际上,我的原始头文件很好用。 - Jesse Bunch

3

我遇到了一个问题已经一个小时了。尝试了10种不同的解决方案后,我解决了它——确保在输出ZIP文件之后脚本存在:

            readfile($zip_name);
            unlink($zip_name);
            **exit();**

1
这是在尝试了上述大多数方法后对我有效的解决方案。 - Michael Rennison

2
            ob_clean(); //very important
            // http headers for zip downloads
            header("Pragma: public");
            header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
            header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
            header("Cache-Control: public");
            header('Content-Type: application/x-download');
            header("Content-Disposition: attachment; filename=\"filename.zip\"");
            header("Content-Length: ".filesize($filepath ));

            @readfile($filepath );
            unlink($filepath);  //very important
            exit;//very important

在尝试上述解决方案时,这对我很有效。


2
除了其他人的建议,重要的是注意文件和目录名称,因为Windows不一定喜欢Linux文件路径和名称。 它有时在压缩时也以不同的方式转义它们。 例子很多,但最重要的是:
  • *点文件(.和..),仅有大小写差异的文件(name.txt和NAME.txt)
  • 绝对文件路径(/tmp/file.txt)*。
  • 在Windows上允许的其他字符在使用Windows Explorer打开文件时可能会引起问题。 在我的情况下,':'字符是关键,但花费了很多力气才找到这一点。

因此,在您使用许多参数通过exec('zip ...')之前,我建议遵循一个简单的过程:

  1. 找到您的网站压缩的文件夹或文件。
  2. 运行:zip -9 -r -k zip-modified-names.zip /path/to/your/folder
  3. 注意控制台输出的内容。 在我的情况下,文件名中的':'被剥离了。
  4. 将zip文件移动到Windows机器并尝试打开它。

如果这样可以,您最好删除已被-k选项剥离的字符,然后尝试正常压缩文件/目录名称。 请注意,某些参数(例如-k)具有副作用。 在这种情况下,-k与-q选项(用于符号链接)相矛盾。

此外,使用PCLZip会使您的生活变得更加轻松,因为您可以一次添加整个文件夹并以一行代码修改文件路径。 在我的情况下,我通过第二和第三个参数从我的zip文件中删除/tmp/directory/,从而避免了另一个Windows兼容性问题(在zip文件中使用绝对路径):

$v_list = $zip->create( $sourceFolder, PCLZIP_OPT_REMOVE_PATH, $sourceFolder . DIRECTORY_SEPARATOR);
if ($v_list == 0) {
    throw new \Exception($zip->errorInfo(true));
}

1

我曾经遇到过同样的问题。这段代码对我有效,但我必须把它放在我的PHP文件的第一行!如果我把代码放在文件中间,它就不起作用。可能是一些编码问题?!

// Prepare File
$file = tempnam("tmp", "zip");
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);

// Staff with content
$zip->addFile($filepathOnServer, 'mypic.jpg');

// Close and send to users
$zip->close();
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="filename.zip"');
readfile($file);
unlink($file);

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