php - ZipArchive() 向存档添加空字节的奇怪行为

标签 php zip ziparchive

我有一个简单的 Zip 创建脚本,可将加载的文件复制到单个目录中,然后从该目录创建一个 .zip 文件。这种方法听起来很简单,但是它产生的文件在打开时有问题。

起初我很困惑,因为文件可以在 7Zip、WinRar 等软件中正常打开。但是,我们无法使用 Windows 内置的存档开启器。为了排除我的主服务器的任何问题,因为它使用 Nginx+PHPfpm + Fedora 16,我还在一个更标准的服务器上进行了测试,使用 Apache 和 mod_php 在 Ubuntu 服务器上运行。

在这两种情况下,问题都是一样的:存档在纯 zip 中也总是可以正常打开,但在 Windows 版本中却失败了。经过一些随机挖掘后,我想到了在 Notepad++ 中打开文件以检查其初始标题的想法。

事实证明 Ziparchive() 正在做两件它不应该做的事情。

第一个问题很简单:它包括完整路径作为存档中的空路径。它不应该是,但它是。这可能是由于我的递归,所以我可以忍受这一点
enter image description here

第二个问题是导致文件无法打开的大问题。它在存档的最开始处预先添加一个空字节。我所要做的就是在 Notepad++ 中手动打开文件,删除字节然后保存它,瞧:文件可以在包括 Windows 在内的所有内容中打开,完全没有问题。

enter image description here **

我以前从未遇到过这种情况,快速的谷歌发现 Ziparchive() 有很多东西/问题,但我找不到任何像这样的具体东西。

这是我的 Zip 创建方法:

private function zipcreate($source, $destination) {
        if (!extension_loaded('zip') || !file_exists($source)) {
            return false;
        }
        $zip = new ZipArchive();
        if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
            return false;
        }
        $source = str_replace('\\', '/', realpath($source));
        if (is_dir($source) === true) {
            $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
            foreach ($files as $file) {
                $file = str_replace('\\', '/', realpath($file));
                if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
                    continue;

                $file = realpath($file);

                if (is_dir($file) === true) {
                    $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
                } else if (is_file($file) === true) {
                    $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
                }
            }
        } else if (is_file($source) === true) {
            $zip->addFromString(basename($source), file_get_contents($source));
        }
        return $zip->close();
    }

通过做调用:
$this->zipcreate($newdirpath, getcwd()."/$siteid-CompliancePack.zip");

对于主服务器上的 phpinfo() 引用:

phpinfo() for primary server

根据要求,文件的前 60 个字节(十六进制)
[root@sid tmp]# od --format=x1 --read-bytes=60 54709-CompliancePack.zip
0000000 50 4b 03 04 14 00 00 00 08 00 39 4e 92 45 59 28
0000020 27 b3 37 53 00 00 00 f2 00 00 36 00 00 00 47 45
0000040 4e 45 52 41 4c 5f 4e 65 77 20 53 69 74 65 20 20
0000060 48 6f 77 61 72 74 68 20 54 69 6d 62
0000074
[root@sid tmp]#

新发展:)
所以我想我会尝试一些完全不同的东西!我在 Windows 桌面上运行了一个 WAMP 堆栈(我通常只在 linux 上测试和开发)。

我在 Windows 机器上运行门户站点,从 Linux 主服务器读取数据与实时门户站点完全相同(唯一的区别是实时门户在 Linux 上运行!)

这次完美创建的文件相差1个字节!这与在同一后端服务器上实时运行的代码完全相同,唯一的区别是用户服务器(门户)代码运行在 Windows 服务器而不是 Linux 上。

Windows File Linux File

该文件由后端服务器创建为 zip,然后进行 base64 编码并通过 Nusoap 返回到门户服务器。然后使用以下代码将文件直接流式传输到客户端浏览器。 SitesClass.downloadCompliancePack 只是一种将所有文件移动到临时文件夹然后运行上面的 zipcreate 方法的方法,所以没有什么神奇的。
$result = $client->call('SitesClass.downloadCompliancePack', array('appusername' => 'xxx','apppassword' => 'xxx','apikey' => 'xxx','siteid' => 54709));
    // Display the result
    header('Content-type: application/octet-stream');
    header('Content-disposition: attachment; filename="54709-CompliancePack.zip"');
    $base = json_decode($result[2]);
    echo base64_decode($base->FileData);

所以现在我更加困惑,因为 Windows 和 linux 之间的简单 base64_decode 不应该有所不同。

2015 年 1 月更新

抱歉,到目前为止所有发布/帮助过的人我一直有点忙,只是有时间看看这个!

我根据下面发布的信息进行了一些测试,并缩小了故障点!我现在确切地知道负责它的代码位。请参阅下面的屏幕截图。
enter image description here

文本区域中的十六进制输出由以下代码创建。
<?php
// get configuration
include "system/config.php";
include "pages/pageclasses/carbon.class.php";
//////////// document action ///////////////
$sid = $_GET['sid'];
// Pull in the NuSOAP code
require_once('lib/nusoap.php');
// Create the client instance
$client = new nusoap_client($api_link); // using nosoap_client
// Call the SOAP method
$result = $client->call('SitesClass.downloadCompliancePack', array('appusername' => $api_username,'apppassword' => $api_password,'apikey' => $api_key,'siteid' => $sid));
// Display the result
$base = json_decode($result[2]);
echo "<textarea>".bin2hex(trim(base64_decode($base->FileData)))."</textarea>";
?>

在它之前显示 20 (Hex Space) 的另一个代码块是
<?php
// get configuration
include "system/config.php";
include "pages/pageclasses/carbon.class.php";
//////////// document action ///////////////
$sid = $_GET['sid'];
// Pull in the NuSOAP code
require_once('lib/nusoap.php');
// Create the client instance
$client = new nusoap_client($api_link); // using nosoap_client
// Call the SOAP method
$result = $client->call('SitesClass.downloadCompliancePack', array('appusername' => $api_username,'apppassword' => $api_password,'apikey' => $api_key,'siteid' => $sid));
// Display the result
header('Content-type:application/octet-stream');
header('Content-disposition:attachment;filename="'.$sid.'-CompliancePack.zip"');
$base = json_decode($result[2]);
echo trim(base64_decode($base->FileData));
?>

在这两种情况下,代码都运行在相同的前端 Web 服务器 (linux) 和相同的后端/Web 服务服务器 (linux) 上,唯一的区别是一个将文件数据输出到 textarea,另一个将文件数据输出到直接流中的浏览器。

两个代码块都是整个文件内容,并且在 php 打开之前或 php 关闭之后没有空格,只是为了安全起见,header() 中没有空格,任何行的末尾也没有空格。

所以现在我处于这个代码块的相当奇怪的情况下,它似乎在流式传输之前向文件中添加了一个随机空间
header('Content-type:application/octet-stream');
header('Content-disposition:attachment;filename="'.$sid.'-CompliancePack.zip"');
echo trim(base64_decode($base->FileData));

最佳答案

关于你的第一个问题,我建议使用

  • ZipArchive::addGlob http://php.net/manual/en/ziparchive.addglob.php
  • ZipArchive::addPattern() http://php.net/manual/en/ziparchive.addpattern.php

  • 这些函数有额外的参数来操作文件名:

    "remove_path"

    Prefix to remove from matching file paths before adding to the archive.



    他们似乎也在做文件系统遍历工作。

    空字符可能与路径有关。

    他们在这里提到了一个与旧文件有关的旧错误:http://grokbase.com/t/php/php-bugs/094pkepf54/48048-new-empty-files-corrupt-zip
    我真的不认为它是相关的,但也许值得尝试删除空文件。 (为了测试。)

    关于php - ZipArchive() 向存档添加空字节的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27419095/

    相关文章:

    php - 如何从远程服务器连接到 ESL?

    php - 如何在 zip 流下载中流式传输 zip?

    objective-c - iOS解压.gz文件

    php - 压缩存档 PHP : Close() returns false with no error message

    php - 在 INI 文件中使用关键字作为变量名

    javascript - 获取 li 作为链接被点击的值

    php - 表格中断 html php,php 的发票报告

    text - 二维码压缩

    python - 将 2 个字典列表压缩为 1

    java - 如何在 Java 中解压缩加密的 ODT OpenDocument