1
0
mirror of https://github.com/splitbrain/php-archive.git synced 2025-01-17 05:28:25 +01:00

Incremental block by block compression for ZIP

This commit is contained in:
Mihail Feraru 2022-03-22 13:15:54 +02:00
parent 726dfddda2
commit 5a132129e9

View File

@ -295,13 +295,88 @@ class Zip extends Archive
throw new ArchiveIOException('Archive has been closed, files can no longer be added');
}
$data = @file_get_contents($file);
if ($data === false) {
$fp = @fopen($file, 'rb');
if ($fp === false) {
throw new ArchiveIOException('Could not open file for reading: '.$file);
}
// FIXME could we stream writing compressed data? gzwrite on a fopen handle?
$this->addData($fileinfo, $data);
$offset = $this->dataOffset();
$name = $fileinfo->getPath();
$time = $fileinfo->getMtime();
// write local file header (temporary CRC and size)
$this->writebytes($this->makeLocalFileHeader(
$time,
0,
0,
0,
$name,
(bool) $this->complevel
));
// we store no encryption header
// prepare info, compress and write data to archive
$deflate_context = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => $this->complevel]);
$crc_context = hash_init('crc32b');
$size = $csize = 0;
while (!feof($fp)) {
$block = fread($fp, 512);
if ($this->complevel) {
$is_first_block = $size === 0;
$is_last_block = feof($fp);
if ($is_last_block) {
$c_block = deflate_add($deflate_context, $block, ZLIB_FINISH);
// get rid of the compression footer
$c_block = substr($c_block, 0, -4);
} else {
$c_block = deflate_add($deflate_context, $block, ZLIB_NO_FLUSH);
}
// get rid of the compression header
if ($is_first_block) {
$c_block = substr($c_block, 2);
}
$csize += strlen($c_block);
$this->writebytes($c_block);
} else {
$this->writebytes($block);
}
$size += strlen($block);
hash_update($crc_context, $block);
}
fclose($fp);
// update the local file header with the computed CRC and size
$crc = hexdec(hash_final($crc_context));
$csize = $this->complevel ? $csize : $size;
$this->writebytesAt($this->makeCrcAndSize(
$crc,
$size,
$csize,
), $offset + self::LOCAL_FILE_HEADER_CRC_OFFSET);
// we store no data descriptor
// add info to central file directory
$this->ctrl_dir[] = $this->makeCentralFileRecord(
$offset,
$time,
$crc,
$size,
$csize,
$name,
(bool) $this->complevel
);
if(is_callable($this->callback)) {
call_user_func($this->callback, $fileinfo);
}
}
/**
@ -709,6 +784,29 @@ class Zip extends Archive
return $written;
}
/**
* Write to the open filepointer or memory at the specified offset
*
* @param string $data
* @param int $offset
* @throws ArchiveIOException
* @return int number of bytes written
*/
protected function writebytesAt($data, $offset) {
if (!$this->file) {
$this->memory .= substr_replace($this->memory, $data, $offset);
$written = strlen($data);
} else {
@fseek($this->fh, $offset);
$written = @fwrite($this->fh, $data);
@fseek($this->fh, 0, SEEK_END);
}
if ($written === false) {
throw new ArchiveIOException('Failed to write to archive stream');
}
return $written;
}
/**
* Current data pointer position
*
@ -825,6 +923,8 @@ class Zip extends Archive
return $header;
}
const LOCAL_FILE_HEADER_CRC_OFFSET = 14;
/**
* Returns a local file header for the given data
*
@ -865,6 +965,22 @@ class Zip extends Archive
return $header;
}
/**
* Returns only a part of the local file header containing the CRC, size and compressed size.
* Used to update these fields for an already written header.
*
* @param int $crc CRC32 checksum of the uncompressed data
* @param int $len length of the uncompressed data
* @param int $clen length of the compressed data
* @return string
*/
protected function makeCrcAndSize($crc, $len, $clen) {
$header = pack('V', $crc); // crc-32
$header .= pack('V', $clen); // compressed size
$header .= pack('V', $len); // uncompressed size
return $header;
}
/**
* Returns an allowed filename and an extra field header
*