887 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			887 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?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);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |