我有一个简单的Zip创建脚本,它将一大堆文件复制到一个目录中,然后从该目录创建一个.zip文件。这种方法听起来很简单,但它产生的档案存在问题。
起初我很困惑,因为档案在7Zip,WinRar等等的东西中打开很好。但是,我们无法使用Windows内置的存档开启工具。为了排除我的主服务器的任何问题,因为它使用Nginx + PHPfpm + Fedora 16我还在一个更标准的服务器上使用在Ubuntu服务器上运行的Apache和mod_php进行了测试。
在这两种情况下,问题都是一样的:存档在纯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(十六进制空格)的另一个代码块
<?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));
答案 0 :(得分:2)
关于你的第一个问题我建议使用
这些函数有额外的参数来操作文件名:
“remove_path”
在添加到匹配文件路径之前从匹配文件路径中删除的前缀 档案
他们似乎也在进行文件系统遍历工作。
空字符可能与路径有关。
他们在这里提到了一个与旧文件有关的旧bug:http://grokbase.com/t/php/php-bugs/094pkepf54/48048-new-empty-files-corrupt-zip 我不认为它是相关的,但也许值得尝试删除空文件。 (进行测试。)
答案 1 :(得分:2)
关于文件开头的附加字节:
在服务器上生成文件似乎没问题。显然问题来自转移/编码过程。
检查实际提供文件的脚本。例如,当您的服务器脚本如下所示:
_<?php readfile('zipfile.zip');
并且在脚本开头有一个空格(用下划线表示)或任何其他字符,它将是输出的一部分。
如果角色不是您脚本的一部分,请检查可能会破坏输出的附带脚本。
根据新代码示例进行更新:
在将二进制数据发送到浏览器之前,尝试清理输出缓冲区:
header('Content-type:application/octet-stream');
header('Content-disposition:attachment;filename="'.$sid.'-CompliancePack.zip"');
ob_clean();
echo trim(base64_decode($base->FileData));