Vendor bunch upload of nelexa library
This commit is contained in:
886
vendor/nelexa/zip/src/IO/ZipWriter.php
vendored
Normal file
886
vendor/nelexa/zip/src/IO/ZipWriter.php
vendored
Normal file
@ -0,0 +1,886 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\IO;
|
||||
|
||||
use PhpZip\Constants\DosCodePage;
|
||||
use PhpZip\Constants\ZipCompressionMethod;
|
||||
use PhpZip\Constants\ZipConstants;
|
||||
use PhpZip\Constants\ZipEncryptionMethod;
|
||||
use PhpZip\Constants\ZipPlatform;
|
||||
use PhpZip\Constants\ZipVersion;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipUnsupportMethodException;
|
||||
use PhpZip\IO\Filter\Cipher\Pkware\PKEncryptionStreamFilter;
|
||||
use PhpZip\IO\Filter\Cipher\WinZipAes\WinZipAesEncryptionStreamFilter;
|
||||
use PhpZip\Model\Data\ZipSourceFileData;
|
||||
use PhpZip\Model\Extra\Fields\ApkAlignmentExtraField;
|
||||
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
|
||||
use PhpZip\Model\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\ZipContainer;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Class ZipWriter.
|
||||
*/
|
||||
class ZipWriter
|
||||
{
|
||||
/** @var int Chunk read size */
|
||||
const CHUNK_SIZE = 8192;
|
||||
|
||||
/** @var ZipContainer */
|
||||
protected $zipContainer;
|
||||
|
||||
/**
|
||||
* ZipWriter constructor.
|
||||
*
|
||||
* @param ZipContainer $container
|
||||
*/
|
||||
public function __construct(ZipContainer $container)
|
||||
{
|
||||
// we clone the container so that the changes made to
|
||||
// it do not affect the data in the ZipFile class
|
||||
$this->zipContainer = clone $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function write($outStream)
|
||||
{
|
||||
if (!\is_resource($outStream)) {
|
||||
throw new \InvalidArgumentException('$outStream must be resource');
|
||||
}
|
||||
$this->beforeWrite();
|
||||
$this->writeLocalBlock($outStream);
|
||||
$cdOffset = ftell($outStream);
|
||||
$this->writeCentralDirectoryBlock($outStream);
|
||||
$cdSize = ftell($outStream) - $cdOffset;
|
||||
$this->writeEndOfCentralDirectoryBlock($outStream, $cdOffset, $cdSize);
|
||||
}
|
||||
|
||||
protected function beforeWrite()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeLocalBlock($outStream)
|
||||
{
|
||||
$zipEntries = $this->zipContainer->getEntries();
|
||||
|
||||
foreach ($zipEntries as $zipEntry) {
|
||||
$this->writeLocalHeader($outStream, $zipEntry);
|
||||
$this->writeData($outStream, $zipEntry);
|
||||
|
||||
if ($zipEntry->isDataDescriptorEnabled()) {
|
||||
$this->writeDataDescriptor($outStream, $zipEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeLocalHeader($outStream, ZipEntry $entry)
|
||||
{
|
||||
// todo in 4.0 version move zipalign functional to ApkWriter class
|
||||
if ($this->zipContainer->isZipAlign()) {
|
||||
$this->zipAlign($outStream, $entry);
|
||||
}
|
||||
|
||||
$relativeOffset = ftell($outStream);
|
||||
$entry->setLocalHeaderOffset($relativeOffset);
|
||||
|
||||
if ($entry->isEncrypted() && $entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) {
|
||||
$entry->enableDataDescriptor(true);
|
||||
}
|
||||
|
||||
$dd = $entry->isDataDescriptorRequired() ||
|
||||
$entry->isDataDescriptorEnabled();
|
||||
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$uncompressedSize = $entry->getUncompressedSize();
|
||||
|
||||
$entry->getLocalExtraFields()->remove(Zip64ExtraField::HEADER_ID);
|
||||
|
||||
if ($compressedSize > ZipConstants::ZIP64_MAGIC || $uncompressedSize > ZipConstants::ZIP64_MAGIC) {
|
||||
$entry->getLocalExtraFields()->add(
|
||||
new Zip64ExtraField($uncompressedSize, $compressedSize)
|
||||
);
|
||||
|
||||
$compressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
$compressionMethod = $entry->getCompressionMethod();
|
||||
$crc = $entry->getCrc();
|
||||
|
||||
if ($entry->isEncrypted() && ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) {
|
||||
/** @var WinZipAesExtraField|null $winZipAesExtra */
|
||||
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipAesExtra === null) {
|
||||
$winZipAesExtra = WinZipAesExtraField::create($entry);
|
||||
}
|
||||
|
||||
if ($winZipAesExtra->isV2()) {
|
||||
$crc = 0;
|
||||
}
|
||||
$compressionMethod = ZipCompressionMethod::WINZIP_AES;
|
||||
}
|
||||
|
||||
$extra = $this->getExtraFieldsContents($entry, true);
|
||||
$name = $entry->getName();
|
||||
$dosCharset = $entry->getCharset();
|
||||
|
||||
if ($dosCharset !== null && !$entry->isUtf8Flag()) {
|
||||
$name = DosCodePage::fromUTF8($name, $dosCharset);
|
||||
}
|
||||
|
||||
$nameLength = \strlen($name);
|
||||
$extraLength = \strlen($extra);
|
||||
|
||||
$size = $nameLength + $extraLength;
|
||||
|
||||
if ($size > 0xffff) {
|
||||
throw new ZipException(
|
||||
sprintf(
|
||||
'%s (the total size of %s bytes for the name, extra fields and comment exceeds the maximum size of %d bytes)',
|
||||
$entry->getName(),
|
||||
$size,
|
||||
0xffff
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$extractedBy = ($entry->getExtractedOS() << 8) | $entry->getExtractVersion();
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VvvvVVVVvv',
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
ZipConstants::LOCAL_FILE_HEADER,
|
||||
// version needed to extract 2 bytes
|
||||
$extractedBy,
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$compressionMethod,
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$dd ? 0 : $crc,
|
||||
// compressed size 4 bytes
|
||||
$dd ? 0 : $compressedSize,
|
||||
// uncompressed size 4 bytes
|
||||
$dd ? 0 : $uncompressedSize,
|
||||
// file name length 2 bytes
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$extraLength
|
||||
)
|
||||
);
|
||||
|
||||
if ($nameLength > 0) {
|
||||
fwrite($outStream, $name);
|
||||
}
|
||||
|
||||
if ($extraLength > 0) {
|
||||
fwrite($outStream, $extra);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function zipAlign($outStream, ZipEntry $entry)
|
||||
{
|
||||
if (!$entry->isDirectory() && $entry->getCompressionMethod() === ZipCompressionMethod::STORED) {
|
||||
$entry->removeExtraField(ApkAlignmentExtraField::HEADER_ID);
|
||||
|
||||
$extra = $this->getExtraFieldsContents($entry, true);
|
||||
$extraLength = \strlen($extra);
|
||||
$name = $entry->getName();
|
||||
|
||||
$dosCharset = $entry->getCharset();
|
||||
|
||||
if ($dosCharset !== null && !$entry->isUtf8Flag()) {
|
||||
$name = DosCodePage::fromUTF8($name, $dosCharset);
|
||||
}
|
||||
$nameLength = \strlen($name);
|
||||
|
||||
$multiple = ApkAlignmentExtraField::ALIGNMENT_BYTES;
|
||||
|
||||
if (StringUtil::endsWith($name, '.so')) {
|
||||
$multiple = ApkAlignmentExtraField::COMMON_PAGE_ALIGNMENT_BYTES;
|
||||
}
|
||||
|
||||
$offset = ftell($outStream);
|
||||
|
||||
$dataMinStartOffset =
|
||||
$offset +
|
||||
ZipConstants::LFH_FILENAME_POS +
|
||||
$extraLength +
|
||||
$nameLength;
|
||||
|
||||
$padding =
|
||||
($multiple - ($dataMinStartOffset % $multiple))
|
||||
% $multiple;
|
||||
|
||||
if ($padding > 0) {
|
||||
$dataMinStartOffset += ApkAlignmentExtraField::MIN_SIZE;
|
||||
$padding =
|
||||
($multiple - ($dataMinStartOffset % $multiple))
|
||||
% $multiple;
|
||||
|
||||
$entry->getLocalExtraFields()->add(
|
||||
new ApkAlignmentExtraField($multiple, $padding)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the local file data fields of the given ZipExtraFields.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
* @param bool $local
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getExtraFieldsContents(ZipEntry $entry, $local)
|
||||
{
|
||||
$local = (bool) $local;
|
||||
$collection = $local ?
|
||||
$entry->getLocalExtraFields() :
|
||||
$entry->getCdExtraFields();
|
||||
$extraData = '';
|
||||
|
||||
foreach ($collection as $extraField) {
|
||||
if ($local) {
|
||||
$data = $extraField->packLocalFileData();
|
||||
} else {
|
||||
$data = $extraField->packCentralDirData();
|
||||
}
|
||||
$extraData .= pack(
|
||||
'vv',
|
||||
$extraField->getHeaderId(),
|
||||
\strlen($data)
|
||||
);
|
||||
$extraData .= $data;
|
||||
}
|
||||
|
||||
$size = \strlen($extraData);
|
||||
|
||||
if ($size > 0xffff) {
|
||||
throw new ZipException(
|
||||
sprintf(
|
||||
'Size extra out of range: %d. Extra data: %s',
|
||||
$size,
|
||||
$extraData
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeData($outStream, ZipEntry $entry)
|
||||
{
|
||||
$zipData = $entry->getData();
|
||||
|
||||
if ($zipData === null) {
|
||||
if ($entry->isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ZipException(sprintf('No zip data for entry "%s"', $entry->getName()));
|
||||
}
|
||||
|
||||
// data write variants:
|
||||
// --------------------
|
||||
// * data of source zip file -> copy compressed data
|
||||
// * store - simple write
|
||||
// * store and encryption - apply encryption filter and simple write
|
||||
// * deflate or bzip2 - apply compression filter and simple write
|
||||
// * (deflate or bzip2) and encryption - create temp stream and apply
|
||||
// compression filter to it, then apply encryption filter to root
|
||||
// stream and write temp stream data.
|
||||
// (PHP cannot apply the filter for encryption after the compression
|
||||
// filter, so a temporary stream is created for the compressed data)
|
||||
|
||||
if ($zipData instanceof ZipSourceFileData && !$zipData->hasRecompressData($entry)) {
|
||||
// data of source zip file -> copy compressed data
|
||||
$zipData->copyCompressedDataToStream($outStream);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$entryStream = $zipData->getDataAsStream();
|
||||
|
||||
if (stream_get_meta_data($entryStream)['seekable']) {
|
||||
rewind($entryStream);
|
||||
}
|
||||
|
||||
$uncompressedSize = $entry->getUncompressedSize();
|
||||
|
||||
$posBeforeWrite = ftell($outStream);
|
||||
$compressionMethod = $entry->getCompressionMethod();
|
||||
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($compressionMethod === ZipCompressionMethod::STORED) {
|
||||
$contextFilter = $this->appendEncryptionFilter($outStream, $entry, $uncompressedSize);
|
||||
$checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize);
|
||||
} else {
|
||||
$compressStream = fopen('php://temp', 'w+b');
|
||||
$contextFilter = $this->appendCompressionFilter($compressStream, $entry);
|
||||
$checksum = $this->writeAndCountChecksum($entryStream, $compressStream, $uncompressedSize);
|
||||
|
||||
if ($contextFilter !== null) {
|
||||
stream_filter_remove($contextFilter);
|
||||
$contextFilter = null;
|
||||
}
|
||||
|
||||
rewind($compressStream);
|
||||
|
||||
$compressedSize = fstat($compressStream)['size'];
|
||||
$contextFilter = $this->appendEncryptionFilter($outStream, $entry, $compressedSize);
|
||||
|
||||
stream_copy_to_stream($compressStream, $outStream);
|
||||
}
|
||||
} else {
|
||||
$contextFilter = $this->appendCompressionFilter($outStream, $entry);
|
||||
$checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize);
|
||||
}
|
||||
|
||||
if ($contextFilter !== null) {
|
||||
stream_filter_remove($contextFilter);
|
||||
$contextFilter = null;
|
||||
}
|
||||
|
||||
// my hack {@see https://bugs.php.net/bug.php?id=49874}
|
||||
fseek($outStream, 0, \SEEK_END);
|
||||
$compressedSize = ftell($outStream) - $posBeforeWrite;
|
||||
|
||||
$entry->setCompressedSize($compressedSize);
|
||||
$entry->setCrc($checksum);
|
||||
|
||||
if (!$entry->isDataDescriptorEnabled()) {
|
||||
if ($uncompressedSize > ZipConstants::ZIP64_MAGIC || $compressedSize > ZipConstants::ZIP64_MAGIC) {
|
||||
/** @var Zip64ExtraField|null $zip64ExtraLocal */
|
||||
$zip64ExtraLocal = $entry->getLocalExtraField(Zip64ExtraField::HEADER_ID);
|
||||
|
||||
// if there is a zip64 extra record, then update it;
|
||||
// if not, write data to data descriptor
|
||||
if ($zip64ExtraLocal !== null) {
|
||||
$zip64ExtraLocal->setCompressedSize($compressedSize);
|
||||
$zip64ExtraLocal->setUncompressedSize($uncompressedSize);
|
||||
|
||||
$posExtra = $entry->getLocalHeaderOffset() + ZipConstants::LFH_FILENAME_POS + \strlen($entry->getName());
|
||||
fseek($outStream, $posExtra);
|
||||
fwrite($outStream, $this->getExtraFieldsContents($entry, true));
|
||||
} else {
|
||||
$posGPBF = $entry->getLocalHeaderOffset() + 6;
|
||||
$entry->enableDataDescriptor(true);
|
||||
fseek($outStream, $posGPBF);
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'v',
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$compressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
$posChecksum = $entry->getLocalHeaderOffset() + 14;
|
||||
|
||||
/** @var WinZipAesExtraField|null $winZipAesExtra */
|
||||
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) {
|
||||
$checksum = 0;
|
||||
}
|
||||
|
||||
fseek($outStream, $posChecksum);
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VVV',
|
||||
// crc-32 4 bytes
|
||||
$checksum,
|
||||
// compressed size 4 bytes
|
||||
$compressedSize,
|
||||
// uncompressed size 4 bytes
|
||||
$uncompressedSize
|
||||
)
|
||||
);
|
||||
fseek($outStream, 0, \SEEK_END);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $inStream
|
||||
* @param resource $outStream
|
||||
* @param int $size
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function writeAndCountChecksum($inStream, $outStream, $size)
|
||||
{
|
||||
$contextHash = hash_init('crc32b');
|
||||
$offset = 0;
|
||||
|
||||
while ($offset < $size) {
|
||||
$read = min(self::CHUNK_SIZE, $size - $offset);
|
||||
$buffer = fread($inStream, $read);
|
||||
fwrite($outStream, $buffer);
|
||||
hash_update($contextHash, $buffer);
|
||||
$offset += $read;
|
||||
}
|
||||
|
||||
return (int) hexdec(hash_final($contextHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipUnsupportMethodException
|
||||
*
|
||||
* @return resource|null
|
||||
*/
|
||||
protected function appendCompressionFilter($outStream, ZipEntry $entry)
|
||||
{
|
||||
$contextCompress = null;
|
||||
switch ($entry->getCompressionMethod()) {
|
||||
case ZipCompressionMethod::DEFLATED:
|
||||
if (!($contextCompress = stream_filter_append(
|
||||
$outStream,
|
||||
'zlib.deflate',
|
||||
\STREAM_FILTER_WRITE,
|
||||
['level' => $entry->getCompressionLevel()]
|
||||
))) {
|
||||
throw new \RuntimeException('Could not append filter "zlib.deflate" to out stream');
|
||||
}
|
||||
break;
|
||||
|
||||
case ZipCompressionMethod::BZIP2:
|
||||
if (!($contextCompress = stream_filter_append(
|
||||
$outStream,
|
||||
'bzip2.compress',
|
||||
\STREAM_FILTER_WRITE,
|
||||
['blocks' => $entry->getCompressionLevel(), 'work' => 0]
|
||||
))) {
|
||||
throw new \RuntimeException('Could not append filter "bzip2.compress" to out stream');
|
||||
}
|
||||
break;
|
||||
|
||||
case ZipCompressionMethod::STORED:
|
||||
// file without compression, do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipUnsupportMethodException(
|
||||
sprintf(
|
||||
'%s (compression method %d (%s) is not supported)',
|
||||
$entry->getName(),
|
||||
$entry->getCompressionMethod(),
|
||||
ZipCompressionMethod::getCompressionMethodName($entry->getCompressionMethod())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $contextCompress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
* @param ZipEntry $entry
|
||||
* @param int $size
|
||||
*
|
||||
* @return resource|null
|
||||
*/
|
||||
protected function appendEncryptionFilter($outStream, ZipEntry $entry, $size)
|
||||
{
|
||||
$encContextFilter = null;
|
||||
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) {
|
||||
PKEncryptionStreamFilter::register();
|
||||
$cipherFilterName = PKEncryptionStreamFilter::FILTER_NAME;
|
||||
} else {
|
||||
WinZipAesEncryptionStreamFilter::register();
|
||||
$cipherFilterName = WinZipAesEncryptionStreamFilter::FILTER_NAME;
|
||||
}
|
||||
$encContextFilter = stream_filter_append(
|
||||
$outStream,
|
||||
$cipherFilterName,
|
||||
\STREAM_FILTER_WRITE,
|
||||
[
|
||||
'entry' => $entry,
|
||||
'size' => $size,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$encContextFilter) {
|
||||
throw new \RuntimeException('Not apply filter ' . $cipherFilterName);
|
||||
}
|
||||
}
|
||||
|
||||
return $encContextFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
protected function writeDataDescriptor($outStream, ZipEntry $entry)
|
||||
{
|
||||
$crc = $entry->getCrc();
|
||||
|
||||
/** @var WinZipAesExtraField|null $winZipAesExtra */
|
||||
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) {
|
||||
$crc = 0;
|
||||
}
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VV',
|
||||
// data descriptor signature 4 bytes (0x08074b50)
|
||||
ZipConstants::DATA_DESCRIPTOR,
|
||||
// crc-32 4 bytes
|
||||
$crc
|
||||
)
|
||||
);
|
||||
|
||||
if (
|
||||
$entry->isZip64ExtensionsRequired() ||
|
||||
$entry->getLocalExtraFields()->has(Zip64ExtraField::HEADER_ID)
|
||||
) {
|
||||
$dd =
|
||||
// compressed size 8 bytes
|
||||
PackUtil::packLongLE($entry->getCompressedSize()) .
|
||||
// uncompressed size 8 bytes
|
||||
PackUtil::packLongLE($entry->getUncompressedSize());
|
||||
} else {
|
||||
$dd = pack(
|
||||
'VV',
|
||||
// compressed size 4 bytes
|
||||
$entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$entry->getUncompressedSize()
|
||||
);
|
||||
}
|
||||
|
||||
fwrite($outStream, $dd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeCentralDirectoryBlock($outStream)
|
||||
{
|
||||
foreach ($this->zipContainer->getEntries() as $outputEntry) {
|
||||
$this->writeCentralDirectoryHeader($outStream, $outputEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Central File Header record.
|
||||
*
|
||||
* @param resource $outStream
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function writeCentralDirectoryHeader($outStream, ZipEntry $entry)
|
||||
{
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$uncompressedSize = $entry->getUncompressedSize();
|
||||
$localHeaderOffset = $entry->getLocalHeaderOffset();
|
||||
|
||||
$entry->getCdExtraFields()->remove(Zip64ExtraField::HEADER_ID);
|
||||
|
||||
if (
|
||||
$localHeaderOffset > ZipConstants::ZIP64_MAGIC ||
|
||||
$compressedSize > ZipConstants::ZIP64_MAGIC ||
|
||||
$uncompressedSize > ZipConstants::ZIP64_MAGIC
|
||||
) {
|
||||
$zip64ExtraField = new Zip64ExtraField();
|
||||
|
||||
if ($uncompressedSize >= ZipConstants::ZIP64_MAGIC) {
|
||||
$zip64ExtraField->setUncompressedSize($uncompressedSize);
|
||||
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
if ($compressedSize >= ZipConstants::ZIP64_MAGIC) {
|
||||
$zip64ExtraField->setCompressedSize($compressedSize);
|
||||
$compressedSize = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
if ($localHeaderOffset >= ZipConstants::ZIP64_MAGIC) {
|
||||
$zip64ExtraField->setLocalHeaderOffset($localHeaderOffset);
|
||||
$localHeaderOffset = ZipConstants::ZIP64_MAGIC;
|
||||
}
|
||||
|
||||
$entry->getCdExtraFields()->add($zip64ExtraField);
|
||||
}
|
||||
|
||||
$extra = $this->getExtraFieldsContents($entry, false);
|
||||
$extraLength = \strlen($extra);
|
||||
|
||||
$name = $entry->getName();
|
||||
$comment = $entry->getComment();
|
||||
|
||||
$dosCharset = $entry->getCharset();
|
||||
|
||||
if ($dosCharset !== null && !$entry->isUtf8Flag()) {
|
||||
$name = DosCodePage::fromUTF8($name, $dosCharset);
|
||||
|
||||
if ($comment) {
|
||||
$comment = DosCodePage::fromUTF8($comment, $dosCharset);
|
||||
}
|
||||
}
|
||||
|
||||
$commentLength = \strlen($comment);
|
||||
|
||||
$compressionMethod = $entry->getCompressionMethod();
|
||||
$crc = $entry->getCrc();
|
||||
|
||||
/** @var WinZipAesExtraField|null $winZipAesExtra */
|
||||
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
|
||||
|
||||
if ($winZipAesExtra !== null) {
|
||||
if ($winZipAesExtra->isV2()) {
|
||||
$crc = 0;
|
||||
}
|
||||
$compressionMethod = ZipCompressionMethod::WINZIP_AES;
|
||||
}
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VvvvvVVVVvvvvvVV',
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
ZipConstants::CENTRAL_FILE_HEADER,
|
||||
// version made by 2 bytes
|
||||
($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(),
|
||||
// version needed to extract 2 bytes
|
||||
($entry->getExtractedOS() << 8) | $entry->getExtractVersion(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$compressionMethod,
|
||||
// last mod file datetime 4 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$crc,
|
||||
// compressed size 4 bytes
|
||||
$compressedSize,
|
||||
// uncompressed size 4 bytes
|
||||
$uncompressedSize,
|
||||
// file name length 2 bytes
|
||||
\strlen($name),
|
||||
// extra field length 2 bytes
|
||||
$extraLength,
|
||||
// file comment length 2 bytes
|
||||
$commentLength,
|
||||
// disk number start 2 bytes
|
||||
0,
|
||||
// internal file attributes 2 bytes
|
||||
$entry->getInternalAttributes(),
|
||||
// external file attributes 4 bytes
|
||||
$entry->getExternalAttributes(),
|
||||
// relative offset of local header 4 bytes
|
||||
$localHeaderOffset
|
||||
)
|
||||
);
|
||||
|
||||
// file name (variable size)
|
||||
fwrite($outStream, $name);
|
||||
|
||||
if ($extraLength > 0) {
|
||||
// extra field (variable size)
|
||||
fwrite($outStream, $extra);
|
||||
}
|
||||
|
||||
if ($commentLength > 0) {
|
||||
// file comment (variable size)
|
||||
fwrite($outStream, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outStream
|
||||
* @param int $centralDirectoryOffset
|
||||
* @param int $centralDirectorySize
|
||||
*/
|
||||
protected function writeEndOfCentralDirectoryBlock(
|
||||
$outStream,
|
||||
$centralDirectoryOffset,
|
||||
$centralDirectorySize
|
||||
) {
|
||||
$cdEntriesCount = \count($this->zipContainer);
|
||||
|
||||
$cdEntriesZip64 = $cdEntriesCount > 0xffff;
|
||||
$cdSizeZip64 = $centralDirectorySize > ZipConstants::ZIP64_MAGIC;
|
||||
$cdOffsetZip64 = $centralDirectoryOffset > ZipConstants::ZIP64_MAGIC;
|
||||
|
||||
$zip64Required = $cdEntriesZip64
|
||||
|| $cdSizeZip64
|
||||
|| $cdOffsetZip64;
|
||||
|
||||
if ($zip64Required) {
|
||||
$zip64EndOfCentralDirectoryOffset = ftell($outStream);
|
||||
|
||||
// find max software version, version needed to extract and most common platform
|
||||
list($softwareVersion, $versionNeededToExtract) = array_reduce(
|
||||
$this->zipContainer->getEntries(),
|
||||
static function (array $carry, ZipEntry $entry) {
|
||||
$carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF);
|
||||
$carry[1] = max($carry[1], $entry->getExtractVersion() & 0xFF);
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[ZipVersion::v10_DEFAULT_MIN, ZipVersion::v45_ZIP64_EXT]
|
||||
);
|
||||
|
||||
$createdOS = $extractedOS = ZipPlatform::OS_DOS;
|
||||
$versionMadeBy = ($createdOS << 8) | max($softwareVersion, ZipVersion::v45_ZIP64_EXT);
|
||||
$versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, ZipVersion::v45_ZIP64_EXT);
|
||||
|
||||
// write zip64 end of central directory signature
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'V',
|
||||
// signature 4 bytes (0x06064b50)
|
||||
ZipConstants::ZIP64_END_CD
|
||||
)
|
||||
);
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
fwrite($outStream, PackUtil::packLongLE(ZipConstants::ZIP64_END_OF_CD_LEN - 12));
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'vvVV',
|
||||
// version made by 2 bytes
|
||||
$versionMadeBy & 0xFFFF,
|
||||
// version needed to extract 2 bytes
|
||||
$versionExtractedBy & 0xFFFF,
|
||||
// number of this disk 4 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
PackUtil::packLongLE($cdEntriesCount) .
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
PackUtil::packLongLE($cdEntriesCount) .
|
||||
// size of the central directory 8 bytes
|
||||
PackUtil::packLongLE($centralDirectorySize) .
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
PackUtil::packLongLE($centralDirectoryOffset)
|
||||
);
|
||||
|
||||
// write zip64 end of central directory locator
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VV',
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
ZipConstants::ZIP64_END_CD_LOC,
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
0
|
||||
) .
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset) .
|
||||
// total number of disks 4 bytes
|
||||
pack('V', 1)
|
||||
);
|
||||
}
|
||||
|
||||
$comment = $this->zipContainer->getArchiveComment();
|
||||
$commentLength = $comment !== null ? \strlen($comment) : 0;
|
||||
|
||||
fwrite(
|
||||
$outStream,
|
||||
pack(
|
||||
'VvvvvVVv',
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
ZipConstants::END_CD,
|
||||
// number of this disk 2 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
// start of the central directory 2 bytes
|
||||
0,
|
||||
// total number of entries in the
|
||||
// central directory on this disk 2 bytes
|
||||
$cdEntriesZip64 ? 0xffff : $cdEntriesCount,
|
||||
// total number of entries in
|
||||
// the central directory 2 bytes
|
||||
$cdEntriesZip64 ? 0xffff : $cdEntriesCount,
|
||||
// size of the central directory 4 bytes
|
||||
$cdSizeZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectorySize,
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 4 bytes
|
||||
$cdOffsetZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectoryOffset,
|
||||
// .ZIP file comment length 2 bytes
|
||||
$commentLength
|
||||
)
|
||||
);
|
||||
|
||||
if ($comment !== null && $commentLength > 0) {
|
||||
// .ZIP file comment (variable size)
|
||||
fwrite($outStream, $comment);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user