【发布时间】:2015-01-13 00:36:51
【问题描述】:
我有一个简单的 Zip 创建脚本,它将大量文件复制到一个目录中,然后从该目录创建一个 .zip 文件。这种方法听起来很简单,但是它生成的档案在打开时会出现问题。
起初我很困惑,因为档案可以在 7Zip、WinRar 等文件中正常打开。但是,我们无法使用 Windows 内置的存档打开器。为了排除我的主服务器使用 Nginx+PHPfpm + Fedora 16 的任何问题,我还在使用 Apache 和 mod_php 在 Ubuntu 服务器上运行的更标准的服务器上进行了测试。
在这两种情况下,问题都是一样的:存档也总是可以在纯 zip 中正常打开,但在 Windows 版本中失败。经过一些随机挖掘后,我想出了在 Notepad++ 中打开文件以检查其初始标题的想法。
事实证明 Ziparchive() 正在做两件不应该做的事情。
第一个问题很简单:它将完整路径作为空路径包含在存档中。它不应该是,但它是。这可能是由于我的递归,所以我可以忍受这一点
第二个问题是导致文件无法打开的大问题。它在存档的开头添加了一个空字节。我所要做的就是在 Notepad++ 中手动打开文件,删除字节然后保存它,瞧:文件在包括 Windows 在内的所有内容中都打开,完全没有问题。
**
我以前从未遇到过这种情况,快速的 Google 发现了很多与 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():
按照要求,文件的前 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 上。
该文件由后端服务器创建为 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);
所以现在我更加困惑,因为简单的 base64_decode 在 windows 和 linux 之间应该没有区别。
2015 年 1 月更新
抱歉,到目前为止所有发布/帮助的人都耽搁了,我有点忙,只是有时间看这个!
我已经根据下面发布的信息进行了一些测试,并缩小了故障点!我现在确切地知道负责它的代码位。请参阅下面的屏幕截图。
文本区域中的十六进制输出由以下代码创建。
<?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(十六进制空格)的另一个代码块是 this
<?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));
【问题讨论】:
-
我试图用您的示例代码重现您的问题,但在开头没有空字节的行为。可能是您将文件从服务器传输到 Windows 机器的工具/方式预先添加了空字节?
-
一开始我确实是这么想的,所以我在服务器上打开了原始文件,而不是下载它,同样的问题发生了。我尝试在 ftp、rsync 上下载并使用我的流式 Web 服务将文件从内部服务器流式传输到外部服务器,并且每次都得到相同的结果。我可以理解这是否是一台服务器上的一个特定问题,但在生成点和下载点两个不同的服务器、2 个不同的 linux 版本、不同的 libgzip 版本、2 个不同的 php 版本甚至不同的 Web 服务器上。我完全不知所措。
-
看你的截图,开头好像不是NULL字符。你能提供前几个字节的十六进制转储吗?
-
@GerdK 我已经为你添加了十六进制的前 60 个字节
-
0x50 0x4B == PK 所以实际上你在 zip 标头之前没有空字节或其他任何内容。
标签: php zip ziparchive