| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886 |
- <?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);
- }
- }
- }
|