ZipArchive

(PHP 5 >= 5.2.0,PHP 7,PHP 8,PECL zip >= 1.1.0)

介绍

用 Zip 压缩的文件存档。

类概要

class ZipArchive implements Countable {
/* 常量 */
public const int CREATE;
public const int EXCL;
public const int CHECKCONS;
public const int OVERWRITE;
public const int RDONLY;
public const int FL_NOCASE;
public const int FL_NODIR;
public const int FL_COMPRESSED;
public const int FL_UNCHANGED;
public const int FL_RECOMPRESS;
public const int FL_ENCRYPTED;
public const int FL_OVERWRITE;
public const int FL_LOCAL;
public const int FL_CENTRAL;
public const int FL_ENC_GUESS;
public const int FL_ENC_RAW;
public const int FL_ENC_STRICT;
public const int FL_ENC_UTF_8;
public const int FL_ENC_CP437;
public const int FL_OPEN_FILE_NOW;
public const int CM_DEFAULT;
public const int CM_STORE;
public const int CM_SHRINK;
public const int CM_REDUCE_1;
public const int CM_REDUCE_2;
public const int CM_REDUCE_3;
public const int CM_REDUCE_4;
public const int CM_IMPLODE;
public const int CM_DEFLATE;
public const int CM_DEFLATE64;
public const int CM_PKWARE_IMPLODE;
public const int CM_BZIP2;
public const int CM_LZMA;
public const int CM_LZMA2;
public const int CM_ZSTD;
public const int CM_XZ;
public const int CM_TERSE;
public const int CM_LZ77;
public const int CM_WAVPACK;
public const int CM_PPMD;
public const int ER_OK;
public const int ER_MULTIDISK;
public const int ER_RENAME;
public const int ER_CLOSE;
public const int ER_SEEK;
public const int ER_READ;
public const int ER_WRITE;
public const int ER_CRC;
public const int ER_ZIPCLOSED;
public const int ER_NOENT;
public const int ER_EXISTS;
public const int ER_OPEN;
public const int ER_TMPOPEN;
public const int ER_ZLIB;
public const int ER_MEMORY;
public const int ER_CHANGED;
public const int ER_COMPNOTSUPP;
public const int ER_EOF;
public const int ER_INVAL;
public const int ER_NOZIP;
public const int ER_INTERNAL;
public const int ER_INCONS;
public const int ER_REMOVE;
public const int ER_DELETED;
public const int ER_ENCRNOTSUPP;
public const int ER_RDONLY;
public const int ER_NOPASSWD;
public const int ER_WRONGPASSWD;
public const int ER_OPNOTSUPP;
public const int ER_INUSE;
public const int ER_TELL;
public const int ER_COMPRESSED_DATA;
public const int ER_CANCELLED;
public const int ER_DATA_LENGTH;
public const int ER_NOT_ALLOWED;
public const int AFL_RDONLY;
public const int AFL_IS_TORRENTZIP;
public const int AFL_WANT_TORRENTZIP;
public const int OPSYS_DOS;
public const int OPSYS_AMIGA;
public const int OPSYS_OPENVMS;
public const int OPSYS_UNIX;
public const int OPSYS_VM_CMS;
public const int OPSYS_ATARI_ST;
public const int OPSYS_OS_2;
public const int OPSYS_MACINTOSH;
public const int OPSYS_Z_SYSTEM;
public const int OPSYS_CPM;
public const int OPSYS_WINDOWS_NTFS;
public const int OPSYS_MVS;
public const int OPSYS_VSE;
public const int OPSYS_ACORN_RISC;
public const int OPSYS_VFAT;
public const int OPSYS_ALTERNATE_MVS;
public const int OPSYS_BEOS;
public const int OPSYS_TANDEM;
public const int OPSYS_OS_400;
public const int OPSYS_OS_X;
public const int OPSYS_DEFAULT;
public const int EM_NONE;
public const int EM_TRAD_PKWARE;
public const int EM_AES_128;
public const int EM_AES_192;
public const int EM_AES_256;
public const int EM_UNKNOWN;
public const string LIBZIP_VERSION;
public const int LENGTH_TO_END;
public const int LENGTH_UNCHECKED;
/* 属性 */
public readonly int $lastId;
public readonly int $status;
public readonly int $statusSys;
public readonly int $numFiles;
public readonly string $filename;
public readonly string $comment;
/* 方法 */
public addEmptyDir(string $dirname, int $flags = 0): bool
public addFile(
    string $filepath,
    string $entryname = "",
    int $start = 0,
    int $length = ZipArchive::LENGTH_TO_END,
    int $flags = ZipArchive::FL_OVERWRITE
): bool
public addFromString(string $name, string $content, int $flags = ZipArchive::FL_OVERWRITE): bool
public addGlob(string $pattern, int $flags = 0, array $options = []): array|false
public addPattern(string $pattern, string $path = ".", array $options = []): array|false
public clearError(): void
public close(): bool
public count(): int
public deleteIndex(int $index): bool
public deleteName(string $name): bool
public extractTo(string $pathto, array|string|null $files = null): bool
public getArchiveComment(int $flags = 0): string|false
public getArchiveFlag(int $flag, int $flags = 0): int
public getCommentIndex(int $index, int $flags = 0): string|false
public getCommentName(string $name, int $flags = 0): string|false
public getExternalAttributesIndex(
    int $index,
    int &$opsys,
    int &$attr,
    int $flags = 0
): bool
public getExternalAttributesName(
    string $name,
    int &$opsys,
    int &$attr,
    int $flags = 0
): bool
public getFromIndex(int $index, int $len = 0, int $flags = 0): string|false
public getFromName(string $name, int $len = 0, int $flags = 0): string|false
public getNameIndex(int $index, int $flags = 0): string|false
public getStreamIndex(int $index, int $flags = 0): resource|false
public getStreamName(string $name, int $flags = 0): resource|false
public static isCompressionMethodSupported(int $method, bool $enc = true): bool
public static isEncryptionMethodSupported(int $method, bool $enc = true): bool
public locateName(string $name, int $flags = 0): int|false
public open(string $filename, int $flags = 0): bool|int
public registerProgressCallback(float $rate, callable $callback): bool
public renameIndex(int $index, string $new_name): bool
public renameName(string $name, string $new_name): bool
public replaceFile(
    string $filepath,
    int $index,
    int $start = 0,
    int $length = ZipArchive::LENGTH_TO_END,
    int $flags = 0
): bool
public setArchiveComment(string $comment): bool
public setArchiveFlag(int $flag, int $value): bool
public setCommentIndex(int $index, string $comment): bool
public setCommentName(string $name, string $comment): bool
public setCompressionIndex(int $index, int $method, int $compflags = 0): bool
public setCompressionName(string $name, int $method, int $compflags = 0): bool
public setEncryptionIndex(int $index, int $method, #[\SensitiveParameter] ?string $password = null): bool
public setEncryptionName(string $name, int $method, #[\SensitiveParameter] ?string $password = null): bool
public setExternalAttributesIndex(
    int $index,
    int $opsys,
    int $attr,
    int $flags = 0
): bool
public setExternalAttributesName(
    string $name,
    int $opsys,
    int $attr,
    int $flags = 0
): bool
public setMtimeIndex(int $index, int $timestamp, int $flags = 0): bool
public setMtimeName(string $name, int $timestamp, int $flags = 0): bool
public statIndex(int $index, int $flags = 0): array|false
public statName(string $name, int $flags = 0): array|false
public unchangeAll(): bool
public unchangeIndex(int $index): bool
public unchangeName(string $name): bool
}

属性

lastId

最后添加的条目(文件或目录)的索引值。从 PHP 8.0.0 和 PECL zip 1.18.0 开始可用。

status

压缩存档的状态。从 PHP 8.0.0 和 PECL zip 1.18.0 开始,适用于封闭存档。

statusSys

压缩存档的系统状态。从 PHP 8.0.0 和 PECL zip 1.18.0 开始,适用于封闭存档。

numFiles

存档中的文件数量

filename

文件系统中的文件名

comment

存档的注释

目录

添加备注

用户贡献笔记 16 笔记

umbalaconmeogia at NOSPAM dot gmail dot com
11 年前
压缩一个文件夹(包括它本身)。
用法
HZip::zipDir('/path/to/sourceDir', '/path/to/out.zip');

<?php
class HZip
{
/**
* 将文件夹中的文件和子目录添加到 zip 文件。
* @param string $folder
* @param ZipArchive $zipFile
* @param int $exclusiveLength 从文件路径中排除的文本数量。
*/
private static function folderToZip($folder, &$zipFile, $exclusiveLength) {
$handle = opendir($folder);
while (
false !== $f = readdir($handle)) {
if (
$f != '.' && $f != '..') {
$filePath = "$folder/$f";
// 在添加到 zip 之前从文件路径中删除前缀。
$localPath = substr($filePath, $exclusiveLength);
if (
is_file($filePath)) {
$zipFile->addFile($filePath, $localPath);
} elseif (
is_dir($filePath)) {
// 添加子目录。
$zipFile->addEmptyDir($localPath);
self::folderToZip($filePath, $zipFile, $exclusiveLength);
}
}
}
closedir($handle);
}

/**
* 压缩一个文件夹(包括它本身)。
* 用法:
* HZip::zipDir('/path/to/sourceDir', '/path/to/out.zip');
*
* @param string $sourcePath 要压缩的目录路径。
* @param string $outZipPath 输出 zip 文件的路径。
*/
public static function zipDir($sourcePath, $outZipPath)
{
$pathInfo = pathInfo($sourcePath);
$parentPath = $pathInfo['dirname'];
$dirName = $pathInfo['basename'];

$z = new ZipArchive();
$z->open($outZipPath, ZIPARCHIVE::CREATE);
$z->addEmptyDir($dirName);
self::folderToZip($sourcePath, $z, strlen("$parentPath/"));
$z->close();
}
}
?>
ohcc at 163 dot com
8 年前
使用 PHP 5.6+,您可能会遇到以下错误。

警告:未知:无法在未知的第 0 行销毁 zip 上下文

警告:ZipArchive::close():无法删除文件:没有这样的文件或目录,位于 xxxx.php 的第 xx 行

示例

警告:未知:无法在未知的第 0 行销毁 zip 上下文

<?php
$za
= new ZipArchive;
$za->open('51-n.com.zip',ZipArchive::CREATE|ZipArchive::OVERWRITE);
?>

警告:ZipArchive::close():无法删除文件:没有这样的文件或目录,位于 xxxx.php 的第 xx 行

<?php
$za
= new ZipArchive;
$za->open('51-n.com.zip',ZipArchive::CREATE|ZipArchive::OVERWRITE);
$za->close();
?>

当压缩存档为空时会发生这种情况。
除非压缩存档中至少包含一个文件,否则它不会被保存到磁盘上。此外,当应用 ZipArchive::OVERWRITE 时,如果存在同名文件,它将在调用 ZipArchive::open() 后被删除。

因此,请不要忘记在您的压缩存档中放入至少一个文件。

<?php
$za
= new ZipArchive;
$za->open('51-n.com.zip',ZipArchive::CREATE|ZipArchive::OVERWRITE);
$za->addFromString('wuxiancheng.cn.txt','yes');
$za->close();
?>
theking2(at)king.ma
1 年前
使用 DirectoryIterator 递归压缩文件夹的现代方法。我使用这个小类

<?php
class MakeZip
{
private
ZipArchive $zipArchive;
private
int $startPathLength; // 用于从存储实体的开头删除的字符数

public function __construct(
string $zipArchivename,
public readonly
string $startPath,
public readonly
mixed $convert_function,
)
{
$this->zipArchive = new \ZipArchive;
$this->zipArchive->open($zipArchivename, ZipArchive::CREATE);
$this->startPathLength = strlen($this-> startPath);

$this-> zipDir($startPath);
}
public function
__destruct()
{
$this-> zipArchive-> close();
}

/**
* 将文件夹中的文件和子目录添加到压缩文件中。
* @param string $folder
* @param ZipArchive $zipFile
* @param int $exclusiveLength 要从文件路径中排除的文本数量。
*/
private function zipDir($folder)
{
echo
$folder . '<br>' . PHP_EOL;
foreach (new
\DirectoryIterator($folder) as $f) {
if (
$f->isDot())
continue;
// 跳过 . ..
if ($f->isDir()) {
if(
$f->getExtension() === 'git') continue; // 跳过 .git 文件夹
$this-> zipArchive-> addEmptyDir($f->getPathname());
$this-> zipDir($f->getPathname());

continue;
}
if (
$f->isFile()) {
if (
$f->getBasename() === basename(__FILE__)) continue; // 跳过自身
if ($f->getExtension() === 'zip') continue; // 跳过 ZIP 文件

$this-> zipArchive ->addFile( substr($f-> getPathname(), $this-> startPathLength) ); // 删除 './'

continue;
}

}
}
}
?>

它可以这样使用
<?php
$host
= str_replace('.', '_', $_SERVER['HTTP_HOST']);
$date = date('Ymd-His');

$zip = new \MakeZip("./archiv-$host-$date.zip", './', $convert_function);
unset(
$zip);
?>

在构造函数中添加加密或其他功能。
AshleyDambra at live dot com
11 年前
简单的 xZip 类,用于将大型文件夹压缩成多个部分,以及一次解压缩多个压缩文件。

<?php
class xZip {
public function
__construct() {}
private function
_rglobRead($source, &$array = array()) {
if (!
$source || trim($source) == "") {
$source = ".";
}
foreach ((array)
glob($source . "/*/") as $key => $value) {
$this->_rglobRead(str_replace("//", "/", $value), $array);
}

foreach ((array)
glob($source . "*.*") as $key => $value) {
$array[] = str_replace("//", "/", $value);
}
}
private function
_zip($array, $part, $destination) {
$zip = new ZipArchive;
@
mkdir($destination, 0777, true);

if (
$zip->open(str_replace("//", "/", "{$destination}/partz{$part}.zip"), ZipArchive::CREATE)) {
foreach ((array)
$array as $key => $value) {
$zip->addFile($value, str_replace(array("../", "./"), NULL, $value));
}
$zip->close();
}
}
public function
zip($limit = 500, $source = NULL, $destination = "./") {
if (!
$destination || trim($destination) == "") {
$destination = "./";
}

$this->_rglobRead($source, $input);
$maxinput = count($input);
$splitinto = (($maxinput / $limit) > round($maxinput / $limit, 0)) ? round($maxinput / $limit, 0) + 1 : round($maxinput / $limit, 0);

for(
$i = 0; $i < $splitinto; $i ++) {
$this->_zip(array_slice($input, ($i * $limit), $limit, true), $i, $destination);
}

unset(
$input);
return;
}
public function
unzip($source, $destination) {
@
mkdir($destination, 0777, true);

foreach ((array)
glob($source . "/*.zip") as $key => $value) {
$zip = new ZipArchive;
if (
$zip->open(str_replace("//", "/", $value)) === true) {
$zip->extractTo($destination);
$zip->close();
}
}
}

public function
__destruct() {}
}

//$zip = new xZip;
//$zip->zip(500, "images/", "images_zip/");
//$zip->unzip("images_zip/", "images/");
?>
Jerry dot Saravia at emc dot com
13 年前
以下代码可用于获取压缩文件中所有文件名的列表。

<?php
$za
= new ZipArchive();

$za->open('theZip.zip');

for(
$i = 0; $i < $za->numFiles; $i++ ){
$stat = $za->statIndex( $i );
print_r( basename( $stat['name'] ) . PHP_EOL );
}
?>
ebestwebmaster at gmail dot com
9 年前
压缩文件并下载的方法
<?php

$files
= array('image.jpeg','text.txt','music.wav');
$zipname = 'enter_any_name_for_the_zipped_file.zip';
$zip = new ZipArchive;
$zip->open($zipname, ZipArchive::CREATE);
foreach (
$files as $file) {
$zip->addFile($file);
}
$zip->close();

/// 然后下载压缩文件。
header('Content-Type: application/zip');
header('Content-disposition: attachment; filename='.$zipname);
header('Content-Length: ' . filesize($zipname));
readfile($zipname);

?>
hardcorevenom at gmx dot com
14 年前
将文件从存档读入变量。
如果发生 CRC32 不匹配,将自动打印警告,我们捕获该警告,以便可以打印我们自己的错误消息。

<?php
$zip
= new ZipArchive();
if (
$zip->open('archive.zip')) {
$fp = $zip->getStream('myfile.txt'); // 压缩包中的文件
if(!$fp)
die(
"Error: can't get stream to zipped file");
$stat = $zip->statName('myfile.txt');

$buf = ""; // 文件缓冲区
ob_start(); // 捕获 CRC 错误消息
while (!feof($fp)) {
$buf .= fread($fp, 2048); // 读取超过 2156 字节似乎会禁用内部 CRC32 验证(bug?)
}
$s = ob_get_contents();
ob_end_clean();
if(
stripos($s, "CRC error") != FALSE){
echo
'CRC32 mismatch, current ';
printf("%08X", crc32($buf)); // 当前 CRC
echo ', expected ';
printf("%08X", $stat['crc']); // 预期 CRC
}

fclose($fp);
$zip->close();
// 完成,解压缩后的文件存储在 $buf 中
}
?>

要创建损坏的文件,请使用十六进制编辑器更改 zip 文件中的一个字节。
bruno dot vibert at bonobox dot fr
12 年前
有一个有用的函数可以将 ZipArchive 状态作为可读的字符串获取

<?php
function ZipStatusString( $status )
{
switch( (int)
$status )
{
case
ZipArchive::ER_OK : return 'N 无错误';
case
ZipArchive::ER_MULTIDISK : return 'N 不支持多磁盘压缩包';
case
ZipArchive::ER_RENAME : return 'S 重命名临时文件失败';
case
ZipArchive::ER_CLOSE : return 'S 关闭压缩包失败';
case
ZipArchive::ER_SEEK : return 'S 搜索错误';
case
ZipArchive::ER_READ : return 'S 读取错误';
case
ZipArchive::ER_WRITE : return 'S 写入错误';
case
ZipArchive::ER_CRC : return 'N CRC 错误';
case
ZipArchive::ER_ZIPCLOSED : return 'N 包含的压缩包已关闭';
case
ZipArchive::ER_NOENT : return 'N 文件不存在';
case
ZipArchive::ER_EXISTS : return 'N 文件已存在';
case
ZipArchive::ER_OPEN : return 'S 无法打开文件';
case
ZipArchive::ER_TMPOPEN : return 'S 创建临时文件失败';
case
ZipArchive::ER_ZLIB : return 'Z Zlib 错误';
case
ZipArchive::ER_MEMORY : return 'N 内存分配失败';
case
ZipArchive::ER_CHANGED : return 'N 条目已更改';
case
ZipArchive::ER_COMPNOTSUPP : return 'N 不支持压缩方法';
case
ZipArchive::ER_EOF : return 'N 文件意外结束';
case
ZipArchive::ER_INVAL : return 'N 无效参数';
case
ZipArchive::ER_NOZIP : return 'N 不是压缩包';
case
ZipArchive::ER_INTERNAL : return 'N 内部错误';
case
ZipArchive::ER_INCONS : return 'N 压缩包不一致';
case
ZipArchive::ER_REMOVE : return 'S 无法删除文件';
case
ZipArchive::ER_DELETED : return 'N 条目已删除';

default: return
sprintf('未知状态 %s', $status );
}
}

?>
nick at fullfatthings dot com
9 年前
PHP 5.3.3 中有一个限制(这似乎在更高版本中得到了解决;5.3.29 在另一台服务器上似乎没问题)。

如果你尝试打开一个包含超过 65,535 个文件的 zip 文件(在我的例子中它有 237,942 个文件),那么你将无法访问后面的文件。numFiles 属性只报告前 65k 个文件。
webmaster at sebastiangrinke dot info
12 年前
这里有一个简单的函数,可以压缩包含所有子文件夹的文件夹或单个文件... $data 变量可以是字符串或数组...

<?php
public function un_zip($data,$arcpf,$mode='zip',$obj=''){
$absoluterpfad = 'YOUR_BASE_PATH';
$arcpf = $absoluterpfad.DS.$arcpf;
if(
is_object($obj)==false){
$archiv = new ZipArchive();
$archiv->open($arcpf,ZipArchive::CREATE);
}else{
$archiv =& $obj;}
if(
$mode=='zip'){
if(
is_array($data)==true){
foreach(
$data as $dtmp){
$archiv =& un_zip($dtmp,$arcpf,'zip',&$archiv);
}
}else{
if(
is_dir($data)==true){
$archiv->addEmptyDir(str_replace($absoluterpfad.DS,'',$data));
$files = scandir($data);
$bad = array('.','..');
$files = array_diff($files,$bad);
foreach(
$files as $ftmp){
if(
is_dir($data.DS.$ftmp)==true){
$archiv->addEmptyDir(str_replace($absoluterpfad.DS,'',$data.'/'.$ftmp));
$archiv =& un_zip($data.DS.$ftmp,$arcpf,'zip',&$archiv);
}elseif(
is_file($data.DS.$ftmp)==true){
$archiv->addFile($data.DS.$ftmp,str_replace($absoluterpfad.DS,'',$data.'/'.$ftmp));
}
}
}elseif(
is_file($data)==true){$archiv->addFile($data,str_replace($absoluterpfad.DS,'',$data));}
}
}
if(
is_object($obj)==false){$archiv->close();}
else{return
$archiv;}
if(
$mode=='unzip'){$archiv->extractTo($data);}
}
?>
niklas dot schumi at NOSPAM dot googlemail dot com
12 年前
您好。
我刚刚写了一个小函数来压缩整个文件夹,同时保持目录结构。希望它能对某些人有所帮助。

<?php
function folderToZip($folder, &$zipFile, $subfolder = null) {
if (
$zipFile == null) {
// 没有提供资源,退出
return false;
}
// 我们检查 $folder 的末尾是否有斜杠,如果没有,我们添加一个
$folder .= end(str_split($folder)) == "/" ? "" : "/";
$subfolder .= end(str_split($subfolder)) == "/" ? "" : "/";
// 我们首先遍历 $folder 中的所有文件
$handle = opendir($folder);
while (
$f = readdir($handle)) {
if (
$f != "." && $f != "..") {
if (
is_file($folder . $f)) {
// 如果找到一个文件,则存储它
// 如果我们有一个子文件夹,则将其存储在那里
if ($subfolder != null)
$zipFile->addFile($folder . $f, $subfolder . $f);
else
$zipFile->addFile($folder . $f);
} elseif (
is_dir($folder . $f)) {
// 如果找到一个文件夹,则在压缩文件中创建一个文件夹
$zipFile->addEmptyDir($f);
// 再次调用函数
folderToZip($folder . $f, $zipFile, $f);
}
}
}
}
?>

使用方法如下
<?php
$z
= new ZipArchive();
$z->open("test.zip", ZIPARCHIVE::CREATE);
folderToZip("storeThisFolder", $z);
$z->close();
?>

祝您有美好的一天!
ohcc at 163 dot com
8 年前
<?php
// 使用 bzip2 + ZipArchive 减少 zip 存档的文件大小。
$zip = new ZipArchive;
$zip->open('i.zip',ZIPARCHIVE::CREATE|ZIPARCHIVE::OVERWRITE);
$file='wuxiancheng.cn.sql';
$bzFilename = $file.'.bz2';
$sql = file_get_contents($file);
$sql = bzcompress($sql,9);
$zip->addFromString($bzFilename,$sql);
$zip->setArchiveComment('zipped on '.date('Y-M-d'));
?>
panique at web dot de
12 年前
重要提示:由于 zip 文件的自然文件大小限制为 4GB(准确地说约为 3.6GB),因此如果结果大于 4GB,此类将生成损坏的文件。使用 tar.gz 是一个合适的替代方案。
piotr dot stop dot spam at gmail dot com
10 年前
您可以检查通用标志以测试 zip 文件是否已加密。下面的示例函数。

<?php

/**
* 检查文件是否已加密
*
* 注意:如果文件不存在或无法打开,函数
* 也将返回 false。
*
* @param string $pathToArchive
* @return boolean 返回 true 如果文件已加密
*/
function isEncryptedZip( $pathToArchive ) {
$fp = @fopen( $pathToArchive, 'r' );
$encrypted = false;
if (
$fp && fseek( $fp, 6 ) == 0 ) {
$string = fread( $fp, 2 );
if (
false !== $string ) {
$data = unpack("vgeneral", $string);
$encrypted = $data[ 'general' ] & 0x01 ? true : false;
}
fclose( $fp );
}
return
$encrypted;
}
h-fate at gmx dot net
13 年前
请注意,有多种算法可以生成 zip 文件。我发现使用 ZipArchive 创建的 Office OpenXML 文件不被 Excel 2007 识别,例如。

在这种情况下,您必须使用不同的类来压缩文件,例如 PclZip。
M. Wolf
7 年前
如何检测 CRC 不匹配的损坏文件

创建损坏的归档文件进行测试很简单 - 压缩一些文件并在生成的 ZIP 文件中使用十六进制编辑器更改一个字节。现在您可以使用 ZIP 应用程序测试该文件,以了解归档文件中哪个文件已损坏。

ZipArchive 似乎无法检测到损坏的文件。ZipArchive::CHECKCONS 无济于事,除非它根本不是 ZIP 文件。在我的测试中,它愉快地解压缩了损坏的文件,而下载数据的用户没有被告知。

您可以简单地为较小的文件在服务器上进行验证
<?php
$maxsize
= 1024*1024;
$z = new ZipArchive;
$r = $z->open("foo.zip", ZipArchive::CHECKCONS);
if(
$r !== TRUE)
die(
'ZIP error when trying to open "foo.zip": '.$r);

$stat = $z->statName("mybrokenfile.txt");
if(
$stat['size'] > $maxsize)
die(
'File too large, decompression denied');
$s = $z->getStream($file);
$data = stream_get_contents($s, $maxsize);
fclose($s);
if(
$stat['crc'] != crc32($data))
die(
'File is corrupt!');
//echo 'File is valid';

//you may send the file to the client now if you didn't output anything before
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="mybrokenfile.txt"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . $stat['size']);
ob_clean();
echo
$data;
$z->close();
?>

如果文件不应该在服务器上完全解压缩,而应该在流式传输到客户端时解压缩,因为它的尺寸很大,并且文件传输已经开始,并且稍后打印错误消息不起作用。也许最好的方法是在关闭文件传输之前中断连接。客户端应该能够将其检测为损坏的下载。
在服务器端,需要一个函数来逐步计算流式传输数据的 CRC32。
To Top